summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules4
-rw-r--r--basicsuite/.gitignore6
-rw-r--r--basicsuite/basicsuite.pro6
-rw-r--r--basicsuite/datacollector/abstractdatasource.cpp42
-rw-r--r--basicsuite/datacollector/abstractdatasource.h71
-rw-r--r--basicsuite/datacollector/datacollector.pro50
-rw-r--r--basicsuite/datacollector/main.cpp63
-rw-r--r--basicsuite/datacollector/mockdatasource.cpp236
-rw-r--r--basicsuite/datacollector/mockdatasource.h90
-rw-r--r--basicsuite/datacollector/socketclient.cpp89
-rw-r--r--basicsuite/datacollector/socketclient.h72
-rw-r--r--basicsuite/datacollector/socketserver.cpp94
-rw-r--r--basicsuite/datacollector/socketserver.h69
-rw-r--r--basicsuite/datacollector/tripdata.cpp253
-rw-r--r--basicsuite/datacollector/tripdata.h115
-rw-r--r--basicsuite/datacollector/unixsignalhandler.cpp149
-rw-r--r--basicsuite/datacollector/unixsignalhandler.h77
-rw-r--r--basicsuite/demos.xml28
-rw-r--r--basicsuite/ebike-ui/.gitignore73
-rw-r--r--basicsuite/ebike-ui/BikeInfoTab.qml483
-rw-r--r--basicsuite/ebike-ui/BikeStyle/Colors.qml125
-rw-r--r--basicsuite/ebike-ui/BikeStyle/UILayout.qml257
-rw-r--r--basicsuite/ebike-ui/BikeStyle/qmldir2
-rw-r--r--basicsuite/ebike-ui/ClockView.qml72
-rw-r--r--basicsuite/ebike-ui/ColumnSpacer.qml45
-rw-r--r--basicsuite/ebike-ui/ConfigurationDrawer.qml147
-rw-r--r--basicsuite/ebike-ui/ConfigurationItem.qml63
-rw-r--r--basicsuite/ebike-ui/FpsItem.qml77
-rw-r--r--basicsuite/ebike-ui/GeneralTab.qml279
-rw-r--r--basicsuite/ebike-ui/IconifiedTabButton.qml70
-rw-r--r--basicsuite/ebike-ui/LightsBox.qml81
-rw-r--r--basicsuite/ebike-ui/MainPage.qml94
-rw-r--r--basicsuite/ebike-ui/ModeBox.qml101
-rw-r--r--basicsuite/ebike-ui/MusicPlayer.qml239
-rw-r--r--basicsuite/ebike-ui/NaviBox.qml129
-rw-r--r--basicsuite/ebike-ui/NaviButton.qml83
-rw-r--r--basicsuite/ebike-ui/NaviGuide.qml139
-rw-r--r--basicsuite/ebike-ui/NaviPage.qml691
-rw-r--r--basicsuite/ebike-ui/NaviTripInfo.qml147
-rw-r--r--basicsuite/ebike-ui/SpeedView.qml526
-rw-r--r--basicsuite/ebike-ui/StatsBox.qml157
-rw-r--r--basicsuite/ebike-ui/StatsPage.qml314
-rw-r--r--basicsuite/ebike-ui/StatsRow.qml102
-rw-r--r--basicsuite/ebike-ui/ToggleSwitch.qml66
-rw-r--r--basicsuite/ebike-ui/TripChart.qml236
-rw-r--r--basicsuite/ebike-ui/ViewTab.qml148
-rw-r--r--basicsuite/ebike-ui/app.pro141
-rw-r--r--basicsuite/ebike-ui/brightnesscontroller.cpp91
-rw-r--r--basicsuite/ebike-ui/brightnesscontroller.h69
-rw-r--r--basicsuite/ebike-ui/datamodelplugin/datamodelplugin.pro39
-rw-r--r--basicsuite/ebike-ui/datamodelplugin/plugin.cpp112
-rw-r--r--basicsuite/ebike-ui/datamodelplugin/qmldir2
-rw-r--r--basicsuite/ebike-ui/datastore.cpp300
-rw-r--r--basicsuite/ebike-ui/datastore.h163
-rw-r--r--basicsuite/ebike-ui/ebike-ui.pro7
-rw-r--r--basicsuite/ebike-ui/ebike_en.ts290
-rw-r--r--basicsuite/ebike-ui/ebike_fi.ts290
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-Black.ttfbin0 -> 194544 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-BlackItalic.ttfbin0 -> 190120 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-Bold.ttfbin0 -> 191648 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-BoldItalic.ttfbin0 -> 195604 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-ExtraBold.ttfbin0 -> 191644 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-ExtraBoldItalic.ttfbin0 -> 191664 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-ExtraLight.ttfbin0 -> 196472 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-ExtraLightItalic.ttfbin0 -> 194348 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-Italic.ttfbin0 -> 191936 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-Light.ttfbin0 -> 192116 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-LightItalic.ttfbin0 -> 192128 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-Medium.ttfbin0 -> 192488 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-MediumItalic.ttfbin0 -> 193096 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-Regular.ttfbin0 -> 190648 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-SemiBold.ttfbin0 -> 192268 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-SemiBoldItalic.ttfbin0 -> 192648 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-Thin.ttfbin0 -> 191468 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Montserrat-ThinItalic.ttfbin0 -> 192540 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/OFL.txt93
-rw-r--r--basicsuite/ebike-ui/fonts/Teko-Bold.ttfbin0 -> 305800 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Teko-Light.ttfbin0 -> 301528 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Teko-Medium.ttfbin0 -> 310028 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Teko-Regular.ttfbin0 -> 311780 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/Teko-SemiBold.ttfbin0 -> 317004 bytes
-rw-r--r--basicsuite/ebike-ui/fonts/fontawesome-webfont.ttfbin0 -> 165548 bytes
-rw-r--r--basicsuite/ebike-ui/fpscounter.cpp79
-rw-r--r--basicsuite/ebike-ui/fpscounter.h77
-rw-r--r--basicsuite/ebike-ui/images/arrow_left.pngbin0 -> 227 bytes
-rw-r--r--basicsuite/ebike-ui/images/arrow_right.pngbin0 -> 236 bytes
-rw-r--r--basicsuite/ebike-ui/images/assist.pngbin0 -> 415 bytes
-rw-r--r--basicsuite/ebike-ui/images/battery.pngbin0 -> 292 bytes
-rw-r--r--basicsuite/ebike-ui/images/bike-battery.pngbin0 -> 11471 bytes
-rw-r--r--basicsuite/ebike-ui/images/bike-brakes.pngbin0 -> 10578 bytes
-rw-r--r--basicsuite/ebike-ui/images/bike-chain.pngbin0 -> 10742 bytes
-rw-r--r--basicsuite/ebike-ui/images/bike-frontwheel.pngbin0 -> 11648 bytes
-rw-r--r--basicsuite/ebike-ui/images/bike-gears.pngbin0 -> 10874 bytes
-rw-r--r--basicsuite/ebike-ui/images/bike-light.pngbin0 -> 10457 bytes
-rw-r--r--basicsuite/ebike-ui/images/bike-rearwheel.pngbin0 -> 11595 bytes
-rw-r--r--basicsuite/ebike-ui/images/blue_circle_gps_area.pngbin0 -> 1089 bytes
-rw-r--r--basicsuite/ebike-ui/images/calories.pngbin0 -> 447 bytes
-rw-r--r--basicsuite/ebike-ui/images/checkmark.pngbin0 -> 208 bytes
-rw-r--r--basicsuite/ebike-ui/images/curtain_shadow_handle.pngbin0 -> 1787 bytes
-rw-r--r--basicsuite/ebike-ui/images/curtain_up_arrow.pngbin0 -> 210 bytes
-rw-r--r--basicsuite/ebike-ui/images/fps_icon.pngbin0 -> 1061 bytes
-rw-r--r--basicsuite/ebike-ui/images/info.pngbin0 -> 439 bytes
-rw-r--r--basicsuite/ebike-ui/images/info_selected.pngbin0 -> 368 bytes
-rw-r--r--basicsuite/ebike-ui/images/lights_off.pngbin0 -> 1325 bytes
-rw-r--r--basicsuite/ebike-ui/images/lights_on.pngbin0 -> 1310 bytes
-rw-r--r--basicsuite/ebike-ui/images/list.pngbin0 -> 179 bytes
-rw-r--r--basicsuite/ebike-ui/images/list_selected.pngbin0 -> 159 bytes
-rw-r--r--basicsuite/ebike-ui/images/map-marker.pngbin0 -> 815 bytes
-rw-r--r--basicsuite/ebike-ui/images/map_btn_shadow.pngbin0 -> 1188 bytes
-rw-r--r--basicsuite/ebike-ui/images/map_destination.pngbin0 -> 973 bytes
-rw-r--r--basicsuite/ebike-ui/images/map_locate.pngbin0 -> 366 bytes
-rw-r--r--basicsuite/ebike-ui/images/map_location_arrow.pngbin0 -> 611 bytes
-rw-r--r--basicsuite/ebike-ui/images/map_zoomin.pngbin0 -> 434 bytes
-rw-r--r--basicsuite/ebike-ui/images/map_zoomout.pngbin0 -> 421 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_bear_l.pngbin0 -> 870 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_bear_r.pngbin0 -> 878 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_hard_l.pngbin0 -> 1113 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_hard_r.pngbin0 -> 1057 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_left.pngbin0 -> 848 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_light_left.pngbin0 -> 1090 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_light_right.pngbin0 -> 1113 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_nodir.pngbin0 -> 820 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_right.pngbin0 -> 799 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_straight.pngbin0 -> 379 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_uturn_l.pngbin0 -> 1283 bytes
-rw-r--r--basicsuite/ebike-ui/images/nav_uturn_r.pngbin0 -> 1287 bytes
-rw-r--r--basicsuite/ebike-ui/images/navigation_widget_shadow.pngbin0 -> 3222 bytes
-rw-r--r--basicsuite/ebike-ui/images/nextsong.pngbin0 -> 344 bytes
-rw-r--r--basicsuite/ebike-ui/images/nextsong_pressed.pngbin0 -> 348 bytes
-rw-r--r--basicsuite/ebike-ui/images/ok.pngbin0 -> 425 bytes
-rw-r--r--basicsuite/ebike-ui/images/pause.pngbin0 -> 173 bytes
-rw-r--r--basicsuite/ebike-ui/images/pause_pressed.pngbin0 -> 172 bytes
-rw-r--r--basicsuite/ebike-ui/images/placeholder.pngbin0 -> 1769 bytes
-rw-r--r--basicsuite/ebike-ui/images/play.pngbin0 -> 362 bytes
-rw-r--r--basicsuite/ebike-ui/images/play_pressed.pngbin0 -> 362 bytes
-rw-r--r--basicsuite/ebike-ui/images/prevsong.pngbin0 -> 349 bytes
-rw-r--r--basicsuite/ebike-ui/images/prevsong_pressed.pngbin0 -> 353 bytes
-rw-r--r--basicsuite/ebike-ui/images/search.pngbin0 -> 463 bytes
-rw-r--r--basicsuite/ebike-ui/images/search_cancel.pngbin0 -> 322 bytes
-rw-r--r--basicsuite/ebike-ui/images/settings.pngbin0 -> 630 bytes
-rw-r--r--basicsuite/ebike-ui/images/settings_selected.pngbin0 -> 445 bytes
-rw-r--r--basicsuite/ebike-ui/images/small_input_box_shadow.pngbin0 -> 1141 bytes
-rw-r--r--basicsuite/ebike-ui/images/small_speedometer_arrow.pngbin0 -> 185 bytes
-rw-r--r--basicsuite/ebike-ui/images/small_speedometer_shadow.pngbin0 -> 3137 bytes
-rw-r--r--basicsuite/ebike-ui/images/speed.pngbin0 -> 400 bytes
-rw-r--r--basicsuite/ebike-ui/images/spinner.pngbin0 -> 3470 bytes
-rw-r--r--basicsuite/ebike-ui/images/top_curtain_drag.pngbin0 -> 1200 bytes
-rw-r--r--basicsuite/ebike-ui/images/trip.pngbin0 -> 381 bytes
-rw-r--r--basicsuite/ebike-ui/images/warning.pngbin0 -> 418 bytes
-rw-r--r--basicsuite/ebike-ui/main.qml350
-rw-r--r--basicsuite/ebike-ui/mapbox.cpp89
-rw-r--r--basicsuite/ebike-ui/mapbox.h68
-rw-r--r--basicsuite/ebike-ui/mapboxsuggestions.cpp109
-rw-r--r--basicsuite/ebike-ui/mapboxsuggestions.h89
-rw-r--r--basicsuite/ebike-ui/moment.js4551
-rw-r--r--basicsuite/ebike-ui/mostrecent.bsonbin0 -> 3440 bytes
-rw-r--r--basicsuite/ebike-ui/navigation.cpp97
-rw-r--r--basicsuite/ebike-ui/navigation.h103
-rw-r--r--basicsuite/ebike-ui/preview_l.jpgbin0 -> 28660 bytes
-rw-r--r--basicsuite/ebike-ui/qml.qrc107
-rw-r--r--basicsuite/ebike-ui/qtquickcontrols2.conf15
-rw-r--r--basicsuite/ebike-ui/socketclient.cpp152
-rw-r--r--basicsuite/ebike-ui/socketclient.h86
-rw-r--r--basicsuite/ebike-ui/suggestionsmodel.cpp157
-rw-r--r--basicsuite/ebike-ui/suggestionsmodel.h82
-rw-r--r--basicsuite/ebike-ui/tripdatamodel.cpp135
-rw-r--r--basicsuite/ebike-ui/tripdatamodel.h96
-rw-r--r--basicsuite/enterprise-charts/BaseChart.qml64
-rw-r--r--basicsuite/enterprise-charts/View1.qml19
-rw-r--r--basicsuite/enterprise-charts/View10.qml19
-rw-r--r--basicsuite/enterprise-charts/View11.qml20
-rw-r--r--basicsuite/enterprise-charts/View12.qml39
-rw-r--r--basicsuite/enterprise-charts/View2.qml15
-rw-r--r--basicsuite/enterprise-charts/View3.qml15
-rw-r--r--basicsuite/enterprise-charts/View4.qml26
-rw-r--r--basicsuite/enterprise-charts/View5.qml16
-rw-r--r--basicsuite/enterprise-charts/View6.qml21
-rw-r--r--basicsuite/enterprise-charts/View7.qml20
-rw-r--r--basicsuite/enterprise-charts/View8.qml21
-rw-r--r--basicsuite/enterprise-charts/View9.qml21
-rw-r--r--basicsuite/enterprise-charts/enterprise-charts.pro3
-rw-r--r--basicsuite/enterprise-charts/main.qml9
-rw-r--r--basicsuite/enterprise-charts/preview_l.jpgbin27257 -> 52504 bytes
-rw-r--r--basicsuite/graphicaleffects/graphicaleffects.pro2
-rw-r--r--basicsuite/graphicaleffects/main.qml33
-rw-r--r--basicsuite/graphicaleffects/preview_l.jpgbin80965 -> 91339 bytes
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/Content.qml14
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/ContentVideo.qml0
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/ControlBar.qml141
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/EffectSelectionPanel.qml34
-rw-r--r--basicsuite/mediaplayer/FileBrowser.qml23
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/ImageButton.qml17
-rw-r--r--basicsuite/mediaplayer/MetadataView.qml24
-rw-r--r--basicsuite/mediaplayer/ParameterPanel.qml19
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/PlaybackControl.qml70
-rw-r--r--basicsuite/mediaplayer/PlayerSlider.qml (renamed from basicsuite/mediaplayer/Slider.qml)28
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/SeekControl.qml37
-rw-r--r--basicsuite/mediaplayer/UrlBar.qml70
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/VolumeControl.qml126
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/images/FullscreenButton.pngbin4304 -> 4304 bytes
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/images/PlaybackSlider.pngbin435 -> 435 bytes
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/images/SliderHandle.pngbin4459 -> 4459 bytes
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/images/SliderProgress.pngbin4461 -> 4461 bytes
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/images/VolumeDown.pngbin4130 -> 4130 bytes
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/images/VolumeUp.pngbin4258 -> 4258 bytes
-rw-r--r--basicsuite/mediaplayer/images/back_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/bluetooth_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/close_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/header_toggle_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/info_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/language_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/mute_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/pause_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/play_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/power_icon.svg1
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/images/qt-logo.pngbin5883 -> 5883 bytes
-rw-r--r--basicsuite/mediaplayer/images/settings_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/settingsmenu_launcher_icon.svg1
-rw-r--r--basicsuite/mediaplayer/images/stop_icon.svg1
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/images/titlebar.sci0
-rw-r--r--basicsuite/mediaplayer/images/volume_icon.svg1
-rw-r--r--[-rwxr-xr-x]basicsuite/mediaplayer/main.qml117
-rw-r--r--basicsuite/mediaplayer/mediaplayer.pro2
-rw-r--r--basicsuite/mediaplayer/preview_l.jpgbin65701 -> 59359 bytes
-rw-r--r--basicsuite/qtquickcontrols2/gallery.cpp82
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20/back.pngbin0 -> 220 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20/drawer.pngbin0 -> 123 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20/menu.pngbin0 -> 123 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@2/back.pngbin0 -> 289 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@2/drawer.pngbin0 -> 126 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@2/menu.pngbin0 -> 158 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@3/back.pngbin0 -> 351 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@3/drawer.pngbin0 -> 130 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@3/menu.pngbin0 -> 193 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@4/back.pngbin0 -> 452 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@4/drawer.pngbin0 -> 131 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/20x20@4/menu.pngbin0 -> 223 bytes
-rw-r--r--basicsuite/qtquickcontrols2/icons/gallery/index.theme24
-rw-r--r--basicsuite/qtquickcontrols2/images/arrow.pngbin0 -> 3060 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/arrow@2x.pngbin0 -> 3240 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/arrow@3x.pngbin0 -> 3370 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/arrow@4x.pngbin0 -> 3615 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/arrows.pngbin0 -> 768 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/arrows@2x.pngbin0 -> 1290 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/arrows@3x.pngbin0 -> 1883 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/arrows@4x.pngbin0 -> 2765 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/qt-logo.pngbin0 -> 2695 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/qt-logo@2x.pngbin0 -> 5521 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/qt-logo@3x.pngbin0 -> 8166 bytes
-rw-r--r--basicsuite/qtquickcontrols2/images/qt-logo@4x.pngbin0 -> 11600 bytes
-rw-r--r--basicsuite/qtquickcontrols2/main.qml315
-rw-r--r--basicsuite/qtquickcontrols2/pages/BusyIndicatorPage.qml73
-rw-r--r--basicsuite/qtquickcontrols2/pages/ButtonPage.qml91
-rw-r--r--basicsuite/qtquickcontrols2/pages/CheckBoxPage.qml87
-rw-r--r--basicsuite/qtquickcontrols2/pages/ComboBoxPage.qml97
-rw-r--r--basicsuite/qtquickcontrols2/pages/DelayButtonPage.qml74
-rw-r--r--basicsuite/qtquickcontrols2/pages/DelegatePage.qml218
-rw-r--r--basicsuite/qtquickcontrols2/pages/DialPage.qml74
-rw-r--r--basicsuite/qtquickcontrols2/pages/DialogPage.qml226
-rw-r--r--basicsuite/qtquickcontrols2/pages/FramePage.qml94
-rw-r--r--basicsuite/qtquickcontrols2/pages/GroupBoxPage.qml95
-rw-r--r--basicsuite/qtquickcontrols2/pages/PageIndicatorPage.qml74
-rw-r--r--basicsuite/qtquickcontrols2/pages/ProgressBarPage.qml80
-rw-r--r--basicsuite/qtquickcontrols2/pages/RadioButtonPage.qml86
-rw-r--r--basicsuite/qtquickcontrols2/pages/RangeSliderPage.qml82
-rw-r--r--basicsuite/qtquickcontrols2/pages/ScrollBarPage.qml87
-rw-r--r--basicsuite/qtquickcontrols2/pages/ScrollIndicatorPage.qml87
-rw-r--r--basicsuite/qtquickcontrols2/pages/ScrollablePage.qml71
-rw-r--r--basicsuite/qtquickcontrols2/pages/SliderPage.qml80
-rw-r--r--basicsuite/qtquickcontrols2/pages/SpinBoxPage.qml76
-rw-r--r--basicsuite/qtquickcontrols2/pages/StackViewPage.qml96
-rw-r--r--basicsuite/qtquickcontrols2/pages/SwipeViewPage.qml96
-rw-r--r--basicsuite/qtquickcontrols2/pages/SwitchPage.qml86
-rw-r--r--basicsuite/qtquickcontrols2/pages/TabBarPage.qml104
-rw-r--r--basicsuite/qtquickcontrols2/pages/TextAreaPage.qml76
-rw-r--r--basicsuite/qtquickcontrols2/pages/TextFieldPage.qml74
-rw-r--r--basicsuite/qtquickcontrols2/pages/ToolTipPage.qml77
-rw-r--r--basicsuite/qtquickcontrols2/pages/TumblerPage.qml73
-rw-r--r--basicsuite/qtquickcontrols2/preview_l.jpgbin0 -> 51411 bytes
-rw-r--r--basicsuite/qtquickcontrols2/qtquickcontrols2.conf8
-rw-r--r--basicsuite/qtquickcontrols2/qtquickcontrols2.pro18
-rw-r--r--basicsuite/qtwebbrowser/preview_l.jpgbin25655 -> 46434 bytes
m---------basicsuite/qtwebbrowser/tqtc-qtwebbrowser0
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/00_HomePage.pngbin0 -> 97167 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/00_HomePage_DeleteBookmarks.pngbin0 -> 84637 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing.pngbin0 -> 30856 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Bookmark.pngbin0 -> 152513 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Loading.pngbin0 -> 295182 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_NoKb.pngbin0 -> 27642 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Private.pngbin0 -> 350083 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_URLbox+Bookmark.pngbin0 -> 29019 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_URLbox.pngbin0 -> 40493 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_Link.pngbin0 -> 69875 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_Text.pngbin0 -> 226165 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_URL.pngbin0 -> 52933 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_1.pngbin0 -> 115301 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_10+.pngbin0 -> 28289 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_2.pngbin0 -> 154532 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_3.pngbin0 -> 181823 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_4.pngbin0 -> 189989 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_5+.pngbin0 -> 202098 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_5.pngbin0 -> 202229 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_New.pngbin0 -> 109443 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/04_Settings.pngbin0 -> 37796 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/Bookmarks_Button.pngbin0 -> 33185 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/LightWebBrowserFunctions.xlsxbin0 -> 14173 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/LightWebBrowser_Specs.pdfbin0 -> 1635131 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/qtwebbrowser.pro2
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/appengine.cpp115
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/appengine.h100
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/main.cpp116
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/navigationhistoryproxymodel.cpp76
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/navigationhistoryproxymodel.h75
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/BrowserWindow.qml473
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/CustomSwitch.qml123
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/FeaturePermissionBar.qml140
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/HomeScreen.qml603
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Keyboard.qml69
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Main.qml50
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/MockTouchPoint.qml63
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/NavigationBar.qml453
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/PageView.qml705
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/SettingsView.qml195
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Utils.js60
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/UIButton.qml77
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/UIToolBar.qml168
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/AppLogoColor.pngbin0 -> 6113 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/AppLogoGrey.pngbin0 -> 5539 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Add.pngbin0 -> 1169 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Back.pngbin0 -> 1184 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmark_Checked.pngbin0 -> 1696 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmark_Indicator.pngbin0 -> 1972 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmarks.pngbin0 -> 1792 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Clear.pngbin0 -> 1467 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Delete.pngbin0 -> 2793 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Forward.pngbin0 -> 1178 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Home.pngbin0 -> 1461 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Reload.pngbin0 -> 1614 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Search.pngbin0 -> 1691 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Settings.pngbin0 -> 1149 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Tabs.pngbin0 -> 1235 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Up.pngbin0 -> 1223 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Error_Icon.pngbin0 -> 4371 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/LightWebBrowser_Icons.svg243
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/about_blank.pngbin0 -> 6022 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/qt.pngbin0 -> 7345 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/touchpoint.pngbin0 -> 1331 bytes
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/resources.qrc37
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/src.pro43
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchmockingapplication.cpp282
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchmockingapplication.h80
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchtracker.cpp169
-rw-r--r--basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchtracker.h97
-rw-r--r--basicsuite/shared/fonts.qrc15
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-Black.ttfbin0 -> 49356 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-Bold.ttfbin0 -> 59908 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-BoldItalic.ttfbin0 -> 69796 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-ExtraLight.ttfbin0 -> 63124 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-ExtraLightItalic.ttfbin0 -> 67788 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-Italic.ttfbin0 -> 72416 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-Light.ttfbin0 -> 64032 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-LightItalic.ttfbin0 -> 71720 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-Regular.ttfbin0 -> 63752 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-SemiBold.ttfbin0 -> 63044 bytes
-rw-r--r--basicsuite/shared/fonts/TitilliumWeb-SemiBoldItalic.ttfbin0 -> 71812 bytes
-rw-r--r--basicsuite/shared/main.cpp51
-rw-r--r--basicsuite/shared/settings.js60
-rw-r--r--basicsuite/shared/settings.qrc5
-rw-r--r--basicsuite/shared/shared.pri19
-rw-r--r--doc/b2qt-demos.qdoc113
-rw-r--r--doc/b2qt-demos.qdocconf10
-rw-r--r--doc/images/b2qt-demo-ebike-ui.jpgbin0 -> 19877 bytes
-rw-r--r--[l---------]doc/images/b2qt-demo-enterprise-charts.jpgbin48 -> 30530 bytes
-rw-r--r--[l---------]doc/images/b2qt-demo-graphicaleffects.jpgbin47 -> 36084 bytes
-rw-r--r--[l---------]doc/images/b2qt-demo-mediaplayer.jpgbin42 -> 27063 bytes
-rw-r--r--doc/images/b2qt-demo-qtquickcontrols2.jpgbin0 -> 12162 bytes
-rw-r--r--[l---------]doc/images/b2qt-demo-qtwebbrowser.jpgbin43 -> 22315 bytes
-rw-r--r--tradeshow/iot-sensortag/SensorTagDemo.pro92
-rw-r--r--tradeshow/iot-sensortag/bluetoothdataprovider.cpp79
-rw-r--r--tradeshow/iot-sensortag/bluetoothdataprovider.h9
-rw-r--r--tradeshow/iot-sensortag/bluetoothdevice.cpp64
-rw-r--r--tradeshow/iot-sensortag/bluetoothdevice.h54
-rw-r--r--tradeshow/iot-sensortag/clouddataprovider.cpp28
-rw-r--r--tradeshow/iot-sensortag/clouddataproviderpool.cpp5
-rw-r--r--tradeshow/iot-sensortag/clouddataproviderpool.h2
-rw-r--r--tradeshow/iot-sensortag/cloudupdate.cpp14
-rw-r--r--tradeshow/iot-sensortag/cloudupdate.h2
-rw-r--r--tradeshow/iot-sensortag/dataproviderpool.cpp41
-rw-r--r--tradeshow/iot-sensortag/dataproviderpool.h23
-rw-r--r--tradeshow/iot-sensortag/demodataproviderpool.cpp122
-rw-r--r--tradeshow/iot-sensortag/demodataproviderpool.h9
-rw-r--r--tradeshow/iot-sensortag/main.cpp145
-rw-r--r--tradeshow/iot-sensortag/mockdataprovider.cpp39
-rw-r--r--tradeshow/iot-sensortag/mockdataprovider.h8
-rw-r--r--tradeshow/iot-sensortag/mockdataproviderpool.cpp7
-rw-r--r--tradeshow/iot-sensortag/mockdataproviderpool.h4
-rw-r--r--tradeshow/iot-sensortag/mqttdataprovider.cpp214
-rw-r--r--tradeshow/iot-sensortag/mqttdataprovider.h83
-rw-r--r--tradeshow/iot-sensortag/mqttdataproviderpool.cpp123
-rw-r--r--tradeshow/iot-sensortag/mqttdataproviderpool.h77
-rw-r--r--tradeshow/iot-sensortag/mqttupdate.cpp376
-rw-r--r--tradeshow/iot-sensortag/mqttupdate.h121
-rw-r--r--tradeshow/iot-sensortag/resources/base/AccelChart.qml1
-rw-r--r--tradeshow/iot-sensortag/resources/base/AltitudeChart.qml1
-rw-r--r--tradeshow/iot-sensortag/resources/base/BaseChart.qml5
-rw-r--r--tradeshow/iot-sensortag/resources/base/GyroChart.qml3
-rw-r--r--tradeshow/iot-sensortag/resources/base/HumidityChart.qml4
-rw-r--r--tradeshow/iot-sensortag/resources/base/MagnetometerChart.qml3
-rw-r--r--tradeshow/iot-sensortag/resources/base/ObjectTemperatureChart.qml2
-rw-r--r--tradeshow/iot-sensortag/resources/base/RotationPage.qml2
-rw-r--r--tradeshow/iot-sensortag/resources/base/SensorSettings.qml270
-rw-r--r--tradeshow/iot-sensortag/resources/base/TemperatureChart.qml3
-rw-r--r--tradeshow/iot-sensortag/resources/base/TopToolbar.qml73
-rw-r--r--tradeshow/iot-sensortag/resources/base/main.qml25
-rw-r--r--tradeshow/iot-sensortag/resources/large/MainLarge.qml177
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/AirPressure/AirPre_base_gauge.pngbin32850 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/AmbientTemperature/ambTemp_display_amb.pngbin7084 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/General/icon_sensor.pngbin932 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/General/separator.pngbin1269 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_base_gauge.pngbin40846 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_max_hum.pngbin1855 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_min_hum.pngbin1926 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/Light/light_base_gauge.pngbin21149 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/Light/light_brightness.pngbin11928 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/ObjectTemperature/objTemp_base_circle.pngbin49793 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/large/images/ObjectTemperature/objTemp_display_obj.pngbin24610 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/MainMedium.qml177
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/AirPressure/AirPre_base_gauge.pngbin23594 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/AmbientTemperature/ambTemp_display_amb.pngbin4744 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/General/icon_sensor.pngbin691 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/General/separator.pngbin966 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_base_gauge.pngbin29413 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_max_hum.pngbin1356 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_min_hum.pngbin1432 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/Light/light_base_gauge.pngbin15242 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/Light/light_brightness.pngbin8479 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/ObjectTemperature/objTemp_base_circle.pngbin35320 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/medium/images/ObjectTemperature/objTemp_display_obj.pngbin17544 -> 0 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/small/MainSmall.qml29
-rw-r--r--tradeshow/iot-sensortag/resources/small/StyleSmall.qml5
-rw-r--r--tradeshow/iot-sensortag/resources/watch/MainWatch.qml301
-rw-r--r--tradeshow/iot-sensortag/resources/watch/StyleWatch.qml (renamed from tradeshow/iot-sensortag/resources/medium/StyleMedium.qml)11
-rw-r--r--tradeshow/iot-sensortag/resources/watch/images/BuiltWithQt.pngbin0 -> 22172 bytes
-rw-r--r--tradeshow/iot-sensortag/resources/watch/images/Toolbar/exit_button.pngbin0 -> 1262 bytes
-rw-r--r--tradeshow/iot-sensortag/sensortagdataprovider.cpp54
-rw-r--r--tradeshow/iot-sensortag/sensortagdataprovider.h14
-rw-r--r--tradeshow/iot-sensortag/sensortagdataproviderpool.cpp77
-rw-r--r--tradeshow/iot-sensortag/sensortagdataproviderpool.h13
-rw-r--r--tradeshow/iot-sensortag/seriesstorage.cpp42
-rw-r--r--tradeshow/iot-sensortag/seriesstorage.h2
-rw-r--r--tradeshow/iot-sensortag/uilarge.qrc17
-rw-r--r--tradeshow/iot-sensortag/uimedium.qrc17
-rw-r--r--tradeshow/iot-sensortag/uiwatch.qrc35
-rw-r--r--wayland/democompositor/.gitignore11
-rw-r--r--wayland/democompositor/apps/README22
-rw-r--r--wayland/democompositor/apps/appentry.cpp (renamed from tradeshow/iot-sensortag/resources/large/StyleLarge.qml)12
-rw-r--r--wayland/democompositor/apps/appentry.h83
-rw-r--r--wayland/democompositor/apps/applistmodel.cpp221
-rw-r--r--wayland/democompositor/apps/applistmodel.h98
-rw-r--r--wayland/democompositor/apps/applog.h55
-rw-r--r--wayland/democompositor/apps/appparser.cpp170
-rw-r--r--wayland/democompositor/apps/appparser.h62
-rw-r--r--wayland/democompositor/apps/apps.pri11
-rw-r--r--wayland/democompositor/democompositor.pro9
-rw-r--r--wayland/democompositor/democompositor.qrc20
-rw-r--r--wayland/democompositor/democompositor_all.pro5
-rw-r--r--wayland/democompositor/main.cpp34
-rw-r--r--wayland/democompositor/processlauncher.cpp124
-rw-r--r--wayland/democompositor/processlauncher.h39
-rw-r--r--wayland/democompositor/qml/Chrome.qml2
-rw-r--r--wayland/democompositor/qml/LaunchButton.qml7
-rw-r--r--wayland/democompositor/qml/Screen.qml71
-rw-r--r--wayland/democompositor/qml/main.qml14
-rw-r--r--wayland/democompositor/resources/apps/clock.app12
-rw-r--r--wayland/democompositor/resources/apps/maps.app9
-rw-r--r--wayland/democompositor/resources/apps/rss.app9
-rw-r--r--wayland/democompositor/resources/apps/stocqt.app9
-rw-r--r--wayland/democompositor/resources/images/Icon_Clocks.png (renamed from wayland/democompositor/images/Icon_Clocks.png)bin1870 -> 1870 bytes
-rw-r--r--wayland/democompositor/resources/images/Icon_Maps.png (renamed from wayland/democompositor/images/Icon_Maps.png)bin1529 -> 1529 bytes
-rw-r--r--wayland/democompositor/resources/images/Icon_RSS.png (renamed from wayland/democompositor/images/Icon_RSS.png)bin1769 -> 1769 bytes
-rw-r--r--wayland/democompositor/resources/images/Icon_StocQt.png (renamed from wayland/democompositor/images/Icon_StocQt.png)bin1639 -> 1639 bytes
-rw-r--r--wayland/democompositor/resources/images/graydot.png (renamed from wayland/democompositor/images/graydot.png)bin211 -> 211 bytes
-rw-r--r--wayland/democompositor/resources/images/graydots.png (renamed from wayland/democompositor/images/graydots.png)bin247 -> 247 bytes
-rw-r--r--wayland/democompositor/resources/images/greendot.png (renamed from wayland/democompositor/images/greendot.png)bin212 -> 212 bytes
-rw-r--r--wayland/democompositor/resources/images/greendots.png (renamed from wayland/democompositor/images/greendots.png)bin240 -> 240 bytes
-rw-r--r--wayland/democompositor/resources/images/icon1.png (renamed from wayland/democompositor/images/icon1.png)bin1700 -> 1700 bytes
-rw-r--r--wayland/democompositor/resources/images/icon2.png (renamed from wayland/democompositor/images/icon2.png)bin1761 -> 1761 bytes
-rw-r--r--wayland/democompositor/resources/images/icon3.png (renamed from wayland/democompositor/images/icon3.png)bin1719 -> 1719 bytes
-rw-r--r--wayland/democompositor/resources/images/icon4.png (renamed from wayland/democompositor/images/icon4.png)bin1505 -> 1505 bytes
-rw-r--r--wayland/democompositor/resources/images/quit.png (renamed from wayland/democompositor/images/quit.png)bin782 -> 782 bytes
-rw-r--r--wayland/democompositor/resources/images/reddot.png (renamed from wayland/democompositor/images/reddot.png)bin219 -> 219 bytes
-rw-r--r--wayland/democompositor/tests/applist/applist.pro17
-rw-r--r--wayland/democompositor/tests/applist/applist.qrc6
-rw-r--r--wayland/democompositor/tests/applist/tst_applistmodel.cpp108
-rw-r--r--wayland/democompositor/tests/apps/apps.pro15
-rw-r--r--wayland/democompositor/tests/apps/apps.qrc5
-rw-r--r--wayland/democompositor/tests/apps/tst_appparser.cpp142
497 files changed, 26202 insertions, 1770 deletions
diff --git a/.gitmodules b/.gitmodules
index 4784266..2ecbb11 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,7 +1,3 @@
[submodule "tradeshow/enterprise-kinectdatavis/src/libfreenect"]
path = tradeshow/enterprise-kinectdatavis/src/libfreenect
url = https://github.com/OpenKinect/libfreenect.git
-[submodule "basicsuite/qtwebbrowser/tqtc-qtwebbrowser"]
- path = basicsuite/qtwebbrowser/tqtc-qtwebbrowser
- url = ssh://codereview.qt-project.org:29418/qt-apps/qtwebbrowser
-
diff --git a/basicsuite/.gitignore b/basicsuite/.gitignore
new file mode 100644
index 0000000..3cb11b4
--- /dev/null
+++ b/basicsuite/.gitignore
@@ -0,0 +1,6 @@
+*.o
+moc_*
+Makefile
+*.pro.user
+*.so
+*.moc
diff --git a/basicsuite/basicsuite.pro b/basicsuite/basicsuite.pro
index b943f4c..e64894b 100644
--- a/basicsuite/basicsuite.pro
+++ b/basicsuite/basicsuite.pro
@@ -9,3 +9,9 @@ qtHaveModule(multimedia) {
SUBDIRS += \
camera
}
+
+qtHaveModule(charts) {
+ SUBDIRS += \
+ ebike-ui \
+ datacollector
+}
diff --git a/basicsuite/datacollector/abstractdatasource.cpp b/basicsuite/datacollector/abstractdatasource.cpp
new file mode 100644
index 0000000..e7e7aa3
--- /dev/null
+++ b/basicsuite/datacollector/abstractdatasource.cpp
@@ -0,0 +1,42 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 "abstractdatasource.h"
+
+AbstractDataSource::AbstractDataSource(QObject *parent)
+ : QObject(parent)
+{
+}
diff --git a/basicsuite/datacollector/abstractdatasource.h b/basicsuite/datacollector/abstractdatasource.h
new file mode 100644
index 0000000..3fc0e1b
--- /dev/null
+++ b/basicsuite/datacollector/abstractdatasource.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 ABSTRACTDATASOURCE_H
+#define ABSTRACTDATASOURCE_H
+
+#include <QObject>
+#include <QJsonObject>
+
+class AbstractDataSource : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit AbstractDataSource(QObject *parent = nullptr);
+ virtual ~AbstractDataSource() {}
+
+public:
+ virtual void setSpeed(double speed)=0;
+ virtual void setTopSpeed(double topSpeed)=0;
+ virtual void setAverageSpeed(double averageSpeed)=0;
+ virtual void setLifetime(double lifetime)=0;
+ virtual void setOdometer(double odometer)=0;
+ virtual void setCalories(double calories)=0;
+ virtual void setBatteryLevel(int batteryLevel)=0;
+ virtual void setAssistDistance(int assistdistance)=0;
+ virtual void setAssistPower(int assistpower)=0;
+ virtual void setCurrentTrip(const QJsonObject &current)=0;
+
+signals:
+ void dataUpdate(const QJsonObject &update);
+
+public slots:
+ virtual void parseMessage(const QJsonObject &message)=0;
+ virtual void shutdown()=0;
+};
+
+#endif // ABSTRACTDATASOURCE_H
diff --git a/basicsuite/datacollector/datacollector.pro b/basicsuite/datacollector/datacollector.pro
new file mode 100644
index 0000000..a01e690
--- /dev/null
+++ b/basicsuite/datacollector/datacollector.pro
@@ -0,0 +1,50 @@
+QT -= gui
+QT += network
+
+CONFIG += c++11 console
+CONFIG -= app_bundle
+
+QMAKE_CXXFLAGS += -Os
+
+if(linux*imx7*g++) {
+ message("Building for IMX7")
+
+ #Use FLTO for better size, other for better speed
+ #(both needs to be used with proper Qt libs)
+
+ if(linux-ffopt-imx7-g++) {
+ }
+ else {
+ DEFINES += USE_FLTO
+ }
+
+ contains(DEFINES,USE_FLTO) {
+ message("Building with FLTO")
+ QMAKE_CXXFLAGS += -flto
+ QMAKE_LFLAGS += -flto
+ } else {
+ message("Building with Sections Optimization")
+ QMAKE_CXXFLAGS += -ffunction-sections -fdata-sections
+ QMAKE_LFLAGS += -Wl,--gc-sections
+ #QMAKE_LFLAGS += -Wl,--print-gc-sections
+ }
+}
+
+SOURCES += main.cpp \
+ socketserver.cpp \
+ socketclient.cpp \
+ abstractdatasource.cpp \
+ mockdatasource.cpp \
+ tripdata.cpp \
+ unixsignalhandler.cpp
+
+HEADERS += \
+ socketserver.h \
+ socketclient.h \
+ abstractdatasource.h \
+ mockdatasource.h \
+ tripdata.h \
+ unixsignalhandler.h
+
+target.path += /data/user/qt/$$TARGET
+INSTALLS += target
diff --git a/basicsuite/datacollector/main.cpp b/basicsuite/datacollector/main.cpp
new file mode 100644
index 0000000..438abbb
--- /dev/null
+++ b/basicsuite/datacollector/main.cpp
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QCoreApplication>
+
+#include "socketserver.h"
+#include "mockdatasource.h"
+#include "unixsignalhandler.h"
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ SocketServer* server = new SocketServer(&app);
+ AbstractDataSource* ds = new MockDataSource(&app);
+ UnixSignalHandler* handler = new UnixSignalHandler(&app);
+
+ QObject::connect(ds, &AbstractDataSource::dataUpdate,
+ server, static_cast<void (SocketServer::*)(const QJsonObject&)>(&SocketServer::sendToClients));
+ QObject::connect(server, &SocketServer::clientMessage,
+ ds, &AbstractDataSource::parseMessage);
+ QObject::connect(handler, &UnixSignalHandler::sigInt,
+ ds, &AbstractDataSource::shutdown);
+ QObject::connect(handler, &UnixSignalHandler::sigInt,
+ &app, &QCoreApplication::quit);
+
+ UnixSignalHandler::setupSignalHandlers();
+
+ return app.exec();
+}
diff --git a/basicsuite/datacollector/mockdatasource.cpp b/basicsuite/datacollector/mockdatasource.cpp
new file mode 100644
index 0000000..1932ce3
--- /dev/null
+++ b/basicsuite/datacollector/mockdatasource.cpp
@@ -0,0 +1,236 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QCoreApplication>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QDir>
+#include <cmath>
+
+#include "mockdatasource.h"
+
+static const char currentTripFilename[] = "currenttrip.bson";
+static const char tripDataFilename[] = "tripdata.bson";
+static const char statisticsFilename[] = "stats.bson";
+
+MockDataSource::MockDataSource(QObject *parent)
+ : AbstractDataSource(parent)
+{
+ connect(&m_updateTimer, &QTimer::timeout, this, &MockDataSource::generateData);
+
+ QDir dir(QCoreApplication::applicationDirPath());
+
+ // Load current status
+ m_dataFile = dir.absoluteFilePath(statisticsFilename);
+ QFile dataFile(m_dataFile);
+ if (dataFile.open(QIODevice::ReadOnly)) {
+ QJsonDocument doc = QJsonDocument::fromBinaryData(dataFile.readAll());
+ m_data = doc.object();
+ m_lifetime = m_data.value("lifetime").toDouble();
+ } else {
+ // Set some default values in case data file did not exist
+ m_data.insert("speed", 0.0);
+ m_data.insert("topspeed", 0.0);
+ m_data.insert("odometer", 0.0);
+ m_data.insert("batterylevel", 100);
+ m_lifetime = 0.0;
+ }
+
+ // Try to load current trip data as well as recorded trip(s)
+ m_currentFile = dir.absoluteFilePath(currentTripFilename);
+ m_current.load(m_currentFile);
+ m_tripsFile = dir.absoluteFilePath(tripDataFilename);
+ m_trips.load(m_tripsFile);
+
+ m_updateTimer.start(100);
+ m_elapsedTimer.start();
+
+ // Generate the list of static trips to be shown on reset
+ m_resettrips << TripData(2100, 6800, 225, 6.944, 3.2, 1515008400.0) // 3.1.2018 11:40
+ << TripData(2700, 10700, 300, 8.333, 2.5, 1515122400.0) // 4.1.2018 19:20
+ << TripData(3900, 13700, 370, 6.944, 5, 1515197100.0) // 5.1.2018 16:05
+ << TripData(1500, 3300, 150, 6.944, 4.7, 1515260100.0) // 6.1.2018 9:35
+ << TripData(4500, 15800, 450, 5.556, 3.2, 1515359700.0); // 7.1.2018 13:15
+}
+
+void MockDataSource::shutdown()
+{
+ m_current.save(m_currentFile);
+ m_trips.save(m_tripsFile);
+}
+
+void MockDataSource::setSpeed(double speed)
+{
+ updateOne("speed", speed);
+}
+
+void MockDataSource::setTopSpeed(double topspeed)
+{
+ updateOne("topspeed", topspeed);
+}
+
+void MockDataSource::setAverageSpeed(double averagespeed)
+{
+ updateOne("averagespeed", averagespeed);
+}
+
+void MockDataSource::setLifetime(double lifetime)
+{
+ updateOne("lifetime", lifetime);
+}
+
+void MockDataSource::setOdometer(double odometer)
+{
+ updateOne("odometer", odometer);
+}
+
+void MockDataSource::setCalories(double calories)
+{
+ updateOne("calories", calories);
+}
+
+void MockDataSource::setBatteryLevel(int batterylevel)
+{
+ updateOne("batterylevel", batterylevel);
+}
+
+void MockDataSource::setAssistDistance(int assistdistance)
+{
+ updateOne("assistdistance", assistdistance);
+}
+
+void MockDataSource::setAssistPower(int assistpower)
+{
+ updateOne("assistpower", assistpower);
+}
+
+void MockDataSource::setCurrentTrip(const QJsonObject &current)
+{
+ emit dataUpdate(QJsonObject{{"method", "currenttrip"}, {"currenttrip", current}});
+}
+
+void MockDataSource::updateOne(const QString &key, const QJsonValue &value)
+{
+ m_data.insert(key, value);
+ emit dataUpdate(QJsonObject{{"method", "updateone"}, {"key", key}, {"value", value}});
+}
+
+void MockDataSource::generateData()
+{
+ static int spd = 1;
+ static int btr = 101;
+
+ double x = static_cast<double>(spd) / 500;
+ double speed = 4.5 * (1.5 + (0.75 * sin(2 * M_PI * x) + 0.5 * sin(6 * M_PI * x - M_PI_2))) - 1.5;
+ double dist = speed / 10; // 10 times per second
+ double elapsed = static_cast<double>(m_elapsedTimer.elapsed()) / 1000.0;
+
+ setAssistPower(speed * 9.5);
+ setSpeed(speed);
+ if (speed > m_data.value("topspeed").toDouble())
+ setTopSpeed(speed);
+ setAverageSpeed(m_data.value("odometer").toDouble() / (m_lifetime + elapsed));
+ setOdometer(m_data.value("odometer").toDouble() + dist);
+ setLifetime(m_lifetime + elapsed);
+
+ // Update trip data
+ if (speed > m_current.maxSpeed())
+ m_current.setMaxSpeed(speed);
+ m_current.addDistance(dist);
+ m_current.addDuration(0.1);
+ m_current.addCalories(0.01);
+
+ setCurrentTrip(m_current);
+
+ if (spd % 50 == 0) {
+ btr -= 1;
+ setBatteryLevel(btr);
+ setAssistDistance(500 * btr);
+ if (btr == 0)
+ btr = 100;
+ }
+
+ // Save the current trip and statistics files every 10 seconds
+ if (spd % 100 == 0) {
+ m_current.save(m_currentFile);
+ QFile dataFile(m_dataFile);
+ if (dataFile.open(QIODevice::WriteOnly)) {
+ QJsonDocument doc(m_data);
+ dataFile.write(doc.toBinaryData());
+ }
+ }
+
+ spd++;
+}
+
+void MockDataSource::parseMessage(const QJsonObject &message)
+{
+ QString method = message.value("method").toString();
+
+ if (method == "set") {
+ m_data.insert(message.value("key").toString(), message.value("value"));
+ } else if (method == "getall") {
+ QJsonArray values;
+ // Iterate over all data
+ QJsonObject::ConstIterator it = m_data.constBegin();
+ while (it != m_data.constEnd()) {
+ values.append(QJsonObject{{"key", it.key()}, {"value", it.value()}});
+ ++it;
+ }
+
+ emit dataUpdate(QJsonObject{{"method", "updatemany"}, {"values", values}});
+ setCurrentTrip(m_current);
+ } else if (method == "gettrips") {
+ emit dataUpdate(QJsonObject{{"method", "trips"}, {"trips", m_trips.toJsonArray()}});
+ } else if (method == "endtrip") {
+ // Append current trip to list of trips and send to frontend
+ m_trips.append(m_current);
+ emit dataUpdate(QJsonObject{{"method", "trip"}, {"trip", m_current.toJsonObject()}});
+ // Then clear it and its save file
+ m_current.clear();
+ m_current.setStartTime();
+ QFile::remove(m_currentFile);
+ // Send update of an empty trip to frontend
+ setCurrentTrip(m_current);
+ // Also save current trip list to file (in case of a crash)
+ m_trips.save(m_tripsFile);
+ } else if (method == "reset") {
+ // Demo only method, to reset trip history
+ m_trips = m_resettrips;
+ emit dataUpdate(QJsonObject{{"method", "reset"}});
+ emit dataUpdate(QJsonObject{{"method", "trips"}, {"trips", m_trips.toJsonArray()}});
+ }
+}
diff --git a/basicsuite/datacollector/mockdatasource.h b/basicsuite/datacollector/mockdatasource.h
new file mode 100644
index 0000000..fe82e19
--- /dev/null
+++ b/basicsuite/datacollector/mockdatasource.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 MOCKDATASOURCE_H
+#define MOCKDATASOURCE_H
+
+#include <QObject>
+#include <QTimer>
+#include <QElapsedTimer>
+#include "abstractdatasource.h"
+#include "tripdata.h"
+
+class MockDataSource: public AbstractDataSource
+{
+ Q_OBJECT
+
+public:
+ explicit MockDataSource(QObject *parent = nullptr);
+
+public:
+ void setSpeed(double speed);
+ void setTopSpeed(double topSpeed);
+ void setAverageSpeed(double averageSpeed);
+ void setLifetime(double lifetime);
+ void setOdometer(double odometer);
+ void setTrip(double trip);
+ void setCalories(double calories);
+ void setBatteryLevel(int batteryLevel);
+ void setAssistDistance(int assistdistance);
+ void setAssistPower(int assistpower);
+ void setCurrentTrip(const QJsonObject &current);
+
+public slots:
+ void parseMessage(const QJsonObject &message);
+ void shutdown();
+
+private:
+ void updateOne(const QString &key, const QJsonValue &value);
+
+private slots:
+ void generateData();
+
+private:
+ QTimer m_updateTimer;
+ QElapsedTimer m_elapsedTimer;
+ double m_lifetime;
+
+ QJsonObject m_data;
+ QString m_dataFile;
+ TripData m_current;
+ Trips m_trips;
+ Trips m_resettrips;
+ QString m_currentFile;
+ QString m_tripsFile;
+};
+
+#endif // MOCKDATASOURCE_H
diff --git a/basicsuite/datacollector/socketclient.cpp b/basicsuite/datacollector/socketclient.cpp
new file mode 100644
index 0000000..a17f3dc
--- /dev/null
+++ b/basicsuite/datacollector/socketclient.cpp
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QDataStream>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+#include "socketclient.h"
+
+SocketClient::SocketClient(QLocalSocket *socket, QObject *parent)
+ : QObject(parent)
+ , m_socket(socket)
+{
+ // Connect socket signals
+ connect(m_socket, &QLocalSocket::connected, this, &SocketClient::connected);
+ connect(m_socket, &QLocalSocket::disconnected, this, &SocketClient::disconnected);
+ connect(m_socket, &QLocalSocket::readyRead, this, &SocketClient::readyRead);
+}
+
+qint64 SocketClient::write(const QByteArray &data) {
+ return m_socket->write(data);
+}
+
+void SocketClient::readyRead() {
+ m_data += m_socket->readAll();
+
+ bool messagefound = true;
+ while (messagefound) {
+ messagefound = false;
+ // If we have at least some data
+ if (m_data.size() >= 4) {
+ // Extract message size
+ qint32 messagesize;
+ QDataStream stream(m_data.left(4));
+ stream >> messagesize;
+
+ // If we have enough data for at least one message
+ if (m_data.size() >= messagesize) {
+ // Extract actual message
+ QByteArray message = m_data.mid(4, messagesize - 4);
+ parseMessage(message);
+ // Drop necessary amount of bytes
+ m_data = m_data.mid(messagesize);
+ messagefound = true; // Try to parse another message
+ }
+ }
+ }
+}
+
+void SocketClient::parseMessage(const QByteArray &message) {
+ // Parse message from raw format
+ QJsonDocument doc = QJsonDocument::fromBinaryData(message);
+ QJsonObject obj = doc.object();
+
+ emit clientMessage(obj);
+}
diff --git a/basicsuite/datacollector/socketclient.h b/basicsuite/datacollector/socketclient.h
new file mode 100644
index 0000000..c489fd5
--- /dev/null
+++ b/basicsuite/datacollector/socketclient.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 SOCKETCLIENT_H
+#define SOCKETCLIENT_H
+
+#include <QObject>
+#include <QLocalSocket>
+
+class SocketClient : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit SocketClient(QLocalSocket *socket, QObject *parent = nullptr);
+
+public:
+ QLocalSocket *socket(void) const { return m_socket; }
+ qint64 write(const QByteArray& data);
+
+private:
+ void parseMessage(const QByteArray& message);
+
+signals:
+ void connected();
+ void disconnected();
+ void clientMessage(const QJsonObject &message);
+
+public slots:
+
+private slots:
+ void readyRead();
+
+private:
+ QLocalSocket *m_socket;
+ QByteArray m_data;
+};
+
+#endif // SOCKETCLIENT_H
diff --git a/basicsuite/datacollector/socketserver.cpp b/basicsuite/datacollector/socketserver.cpp
new file mode 100644
index 0000000..9391316
--- /dev/null
+++ b/basicsuite/datacollector/socketserver.cpp
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QLocalServer>
+#include <QJsonDocument>
+#include <QDataStream>
+
+#include "socketserver.h"
+#include "socketclient.h"
+
+SocketServer::SocketServer(QObject *parent)
+ : QObject(parent)
+ , m_server(new QLocalServer(this))
+{
+ connect(m_server, &QLocalServer::newConnection, this, &SocketServer::processConnected);
+ QLocalServer::removeServer("datasocket");
+ m_server->listen("datasocket");
+}
+
+void SocketServer::processConnected(void) {
+ while (m_server->hasPendingConnections()) {
+ QLocalSocket* socket = m_server->nextPendingConnection();
+ qDebug() << "Client connected" << socket;
+ if (socket) {
+ SocketClient *client = new SocketClient(socket, this);
+ connect(client, &SocketClient::disconnected, this, &SocketServer::processDisconnected);
+ connect(client, &SocketClient::clientMessage, this, &SocketServer::clientMessage);
+ m_clients.append(client);
+ }
+ }
+}
+
+void SocketServer::processDisconnected(void) {
+ SocketClient *client = qobject_cast<SocketClient*>(sender());
+ qDebug() << "Client disconnected" << client->socket();
+ m_clients.removeOne(client);
+ client->deleteLater();
+}
+
+void SocketServer::sendToClients(const QByteArray &message) {
+ // Prepend the message length
+ QByteArray data;
+ QDataStream stream(&data, QIODevice::WriteOnly);
+ stream << static_cast<qint32>(message.size() + 4);
+ data.append(message);
+
+ foreach (SocketClient *client, m_clients) {
+ client->write(data);
+ }
+}
+
+/**
+ * @brief sendToClients
+ * @param message
+ *
+ * Converts a JSON object to its binary representation and sends it to all clients
+ */
+void SocketServer::sendToClients(const QJsonObject &message) {
+ QJsonDocument doc(message);
+ sendToClients(doc.toBinaryData());
+}
diff --git a/basicsuite/datacollector/socketserver.h b/basicsuite/datacollector/socketserver.h
new file mode 100644
index 0000000..e48232c
--- /dev/null
+++ b/basicsuite/datacollector/socketserver.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 SOCKETSERVER_H
+#define SOCKETSERVER_H
+
+#include <QObject>
+#include <QJsonObject>
+
+class QLocalServer;
+class SocketClient;
+
+class SocketServer: public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit SocketServer(QObject *parent = nullptr);
+
+signals:
+ void clientMessage(const QJsonObject &message);
+
+public slots:
+ void sendToClients(const QByteArray &message);
+ void sendToClients(const QJsonObject &message);
+
+private slots:
+ void processConnected();
+ void processDisconnected();
+
+private:
+ QLocalServer *m_server;
+ QList<SocketClient *> m_clients;
+};
+
+#endif // SOCKETSERVER_H
diff --git a/basicsuite/datacollector/tripdata.cpp b/basicsuite/datacollector/tripdata.cpp
new file mode 100644
index 0000000..7ce5590
--- /dev/null
+++ b/basicsuite/datacollector/tripdata.cpp
@@ -0,0 +1,253 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QJsonDocument>
+#include <QJsonArray>
+#include <QFile>
+#include <QDateTime>
+
+#include "tripdata.h"
+
+TripData::TripData()
+ : m_duration(0.0)
+ , m_distance(0.0)
+ , m_calories(0.0)
+ , m_maxspeed(0.0)
+ , m_ascent(0.0)
+ , m_starttime(static_cast<double>(QDateTime::currentMSecsSinceEpoch()) / 1000.0)
+{
+}
+
+TripData::TripData(double duration, double distance, double calories,
+ double maxspeed, double ascent, double starttime)
+ : m_duration(duration)
+ , m_distance(distance)
+ , m_calories(calories)
+ , m_maxspeed(maxspeed)
+ , m_ascent(ascent)
+ , m_starttime(starttime)
+{
+}
+
+TripData::TripData(const QJsonObject &data)
+ : m_duration(data.value("duration").toDouble())
+ , m_distance(data.value("distance").toDouble())
+ , m_calories(data.value("calories").toDouble())
+ , m_maxspeed(data.value("maxspeed").toDouble())
+ , m_ascent(data.value("ascent").toDouble())
+ , m_starttime(data.value("starttime").toDouble())
+{
+}
+
+bool TripData::load(const QString &filename)
+{
+ QFile jsonfile(filename);
+ if (jsonfile.open(QIODevice::ReadOnly)) {
+ QJsonDocument doc = QJsonDocument::fromBinaryData(jsonfile.readAll());
+ QJsonObject data = doc.object();
+
+ m_duration = data.value("duration").toDouble();
+ m_distance = data.value("distance").toDouble();
+ m_calories = data.value("calories").toDouble();
+ m_maxspeed = data.value("maxspeed").toDouble();
+ m_ascent = data.value("ascent").toDouble();
+ m_starttime = data.value("starttime").toDouble();
+
+ return true;
+ }
+ return false;
+}
+
+void TripData::save(const QString &filename) const
+{
+ QFile jsonfile(filename);
+ if (jsonfile.open(QIODevice::WriteOnly)) {
+ QJsonDocument doc(toJsonObject());
+ jsonfile.write(doc.toBinaryData());
+ }
+}
+
+void TripData::setDuration(double duration)
+{
+ if (qFuzzyCompare(m_duration, duration))
+ return;
+
+ m_duration = duration;
+}
+
+void TripData::setDistance(double distance)
+{
+ if (qFuzzyCompare(m_distance, distance))
+ return;
+
+ m_distance = distance;
+}
+
+void TripData::setCalories(double calories)
+{
+ if (qFuzzyCompare(m_calories, calories))
+ return;
+
+ m_calories = calories;
+}
+
+void TripData::setMaxSpeed(double maxspeed)
+{
+ if (qFuzzyCompare(m_maxspeed, maxspeed))
+ return;
+
+ m_maxspeed = maxspeed;
+}
+
+void TripData::setAscent(double ascent)
+{
+ if (qFuzzyCompare(m_ascent, ascent))
+ return;
+
+ m_ascent = ascent;
+}
+
+void TripData::setStartTime(double starttime)
+{
+ // Special value means reset to current timestamp
+ if (starttime == 0.0)
+ starttime = static_cast<double>(QDateTime::currentMSecsSinceEpoch()) / 1000.0;
+
+ if (qFuzzyCompare(m_starttime, starttime))
+ return;
+
+ m_starttime = starttime;
+}
+
+void TripData::addDuration(double duration)
+{
+ m_duration += duration;
+}
+
+void TripData::addDistance(double distance)
+{
+ m_distance += distance;
+}
+
+void TripData::addCalories(double calories)
+{
+ m_calories += calories;
+}
+
+void TripData::clear()
+{
+ m_duration = 0.0;
+ m_distance = 0.0;
+ m_calories = 0.0;
+ m_maxspeed = 0.0;
+ m_ascent = 0.0;
+ m_starttime = 0.0;
+}
+
+TripData::operator QJsonObject() const
+{
+ return toJsonObject();
+}
+
+const QJsonObject TripData::toJsonObject(void) const
+{
+ QJsonObject data;
+
+ data.insert("duration", m_duration);
+ data.insert("distance", m_distance);
+ data.insert("calories", m_calories);
+ data.insert("maxspeed", m_maxspeed);
+ data.insert("avgspeed", m_distance / m_duration);
+ data.insert("ascent", m_ascent);
+ data.insert("starttime", m_starttime);
+
+ return data;
+}
+
+TripData TripData::fromJsonObject(const QJsonObject &data)
+{
+ return TripData(data);
+}
+
+TripData TripData::fromFile(const QString &filename)
+{
+ TripData trip;
+ trip.load(filename);
+ return trip;
+}
+
+bool Trips::load(const QString &filename)
+{
+ QFile jsonfile(filename);
+ if (jsonfile.open(QIODevice::ReadOnly)) {
+ QJsonDocument doc = QJsonDocument::fromBinaryData(jsonfile.readAll());
+ clear();
+ foreach (const QJsonValue &trip, doc.array())
+ append(trip.toObject());
+ return true;
+ }
+ return false;
+}
+
+void Trips::save(const QString &filename) const
+{
+ QFile jsonfile(filename);
+ if (jsonfile.open(QIODevice::WriteOnly)) {
+ QJsonDocument doc(*this);
+ jsonfile.write(doc.toBinaryData());
+ }
+}
+
+Trips::operator QJsonArray() const
+{
+ return toJsonArray();
+}
+
+const QJsonArray Trips::toJsonArray() const
+{
+ QJsonArray array;
+ foreach (const TripData &data, *this)
+ array.append(data.toJsonObject());
+
+ return array;
+}
+
+Trips Trips::fromFile(const QString &filename)
+{
+ Trips trips;
+ trips.load(filename);
+ return trips;
+}
diff --git a/basicsuite/datacollector/tripdata.h b/basicsuite/datacollector/tripdata.h
new file mode 100644
index 0000000..36fd9c8
--- /dev/null
+++ b/basicsuite/datacollector/tripdata.h
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 TRIPDATA_H
+#define TRIPDATA_H
+
+#include <QElapsedTimer>
+#include <QJsonObject>
+#include <QList>
+
+class TripData
+{
+public:
+ TripData();
+ TripData(double duration, double distance, double calories,
+ double maxspeed, double ascent, double starttime);
+ TripData(const QJsonObject &data);
+
+public:
+ double duration() const { return m_duration; }
+ double distance() const { return m_distance; }
+ double calories() const { return m_calories; }
+ double maxSpeed() const { return m_maxspeed; }
+ double avgSpeed() const { return m_distance / m_duration; }
+ double ascent() const { return m_ascent; }
+ double startTime() const { return m_starttime; }
+
+ void setDuration(double duration);
+ void setDistance(double distance);
+ void setCalories(double calories);
+ void setMaxSpeed(double maxspeed);
+ void setAscent(double ascent);
+ void setStartTime(double starttime=0);
+ void addDuration(double duration);
+ void addDistance(double distance);
+ void addCalories(double calories);
+ void clear();
+
+ // Load from file
+ bool load(const QString &filename);
+
+ // Save to file
+ void save(const QString &filename) const;
+
+ // Convert to Json Object
+ operator QJsonObject() const;
+ const QJsonObject toJsonObject(void) const;
+
+ // Create from Json
+ static TripData fromJsonObject(const QJsonObject &data);
+
+ // Load from binary Json data
+ static TripData fromFile(const QString &filename);
+
+private:
+ QElapsedTimer m_timer;
+ double m_duration; // Seconds
+ double m_distance; // Meters
+ double m_calories; // calories, not kcal
+ double m_maxspeed; // m/s
+ double m_ascent; // meters
+ double m_starttime; // Timestamp of the start of the trip
+};
+
+class Trips: public QList<TripData>
+{
+public:
+ // Load from file
+ bool load(const QString &filename);
+
+ // Save to file
+ void save(const QString &filename) const;
+
+ // Convert to Json Array
+ operator QJsonArray() const;
+ const QJsonArray toJsonArray(void) const;
+
+ // Load from binary Json data
+ static Trips fromFile(const QString &filename);
+};
+
+#endif // TRIPDATA_H
diff --git a/basicsuite/datacollector/unixsignalhandler.cpp b/basicsuite/datacollector/unixsignalhandler.cpp
new file mode 100644
index 0000000..61fd87b
--- /dev/null
+++ b/basicsuite/datacollector/unixsignalhandler.cpp
@@ -0,0 +1,149 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QSocketNotifier>
+#include <signal.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include "unixsignalhandler.h"
+
+int UnixSignalHandler::sigintFd[2] = {0};
+int UnixSignalHandler::sighupFd[2] = {0};
+int UnixSignalHandler::sigtermFd[2] = {0};
+
+UnixSignalHandler::UnixSignalHandler(QObject *parent)
+ : QObject(parent)
+{
+ if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd))
+ qFatal("Couldn't create INT socketpair");
+
+ if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sighupFd))
+ qFatal("Couldn't create HUP socketpair");
+
+ if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigtermFd))
+ qFatal("Couldn't create TERM socketpair");
+
+ m_sigintNotifier = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this);
+ connect(m_sigintNotifier, &QSocketNotifier::activated,
+ this, &UnixSignalHandler::handleSigInt);
+ m_sighupNotifier = new QSocketNotifier(sighupFd[1], QSocketNotifier::Read, this);
+ connect(m_sighupNotifier, &QSocketNotifier::activated,
+ this, &UnixSignalHandler::handleSigHup);
+ m_sigtermNotifier = new QSocketNotifier(sigtermFd[1], QSocketNotifier::Read, this);
+ connect(m_sigtermNotifier, &QSocketNotifier::activated,
+ this, &UnixSignalHandler::handleSigTerm);
+}
+
+int UnixSignalHandler::setupSignalHandlers()
+{
+ struct sigaction sig_int, sig_hup, sig_term;
+
+ sig_int.sa_handler = UnixSignalHandler::sigIntHandler;
+ sigemptyset(&sig_int.sa_mask);
+ sig_int.sa_flags = 0;
+ sig_int.sa_flags |= SA_RESTART;
+
+ if (sigaction(SIGINT, &sig_int, 0))
+ return 1;
+
+ sig_hup.sa_handler = UnixSignalHandler::sigHupHandler;
+ sigemptyset(&sig_hup.sa_mask);
+ sig_hup.sa_flags = 0;
+ sig_hup.sa_flags |= SA_RESTART;
+
+ if (sigaction(SIGHUP, &sig_hup, 0))
+ return 2;
+
+ sig_term.sa_handler = UnixSignalHandler::sigTermHandler;
+ sigemptyset(&sig_term.sa_mask);
+ sig_term.sa_flags |= SA_RESTART;
+
+ if (sigaction(SIGTERM, &sig_term, 0))
+ return 3;
+
+ return 0;
+}
+
+void UnixSignalHandler::sigIntHandler(int)
+{
+ char a = 1;
+ ::write(sigintFd[0], &a, sizeof(a));
+}
+
+void UnixSignalHandler::sigHupHandler(int)
+{
+ char a = 1;
+ ::write(sighupFd[0], &a, sizeof(a));
+}
+
+void UnixSignalHandler::sigTermHandler(int)
+{
+ char a = 1;
+ ::write(sigtermFd[0], &a, sizeof(a));
+}
+
+void UnixSignalHandler::handleSigInt()
+{
+ m_sigintNotifier->setEnabled(false);
+ char tmp;
+ ::read(sigintFd[1], &tmp, sizeof(tmp));
+
+ emit sigInt();
+
+ m_sigintNotifier->setEnabled(true);
+}
+
+void UnixSignalHandler::handleSigHup()
+{
+ m_sighupNotifier->setEnabled(false);
+ char tmp;
+ ::read(sighupFd[1], &tmp, sizeof(tmp));
+
+ emit sigHup();
+
+ m_sighupNotifier->setEnabled(true);
+}
+
+void UnixSignalHandler::handleSigTerm()
+{
+ m_sigtermNotifier->setEnabled(false);
+ char tmp;
+ ::read(sigtermFd[1], &tmp, sizeof(tmp));
+
+ emit sigTerm();
+
+ m_sigtermNotifier->setEnabled(true);
+}
diff --git a/basicsuite/datacollector/unixsignalhandler.h b/basicsuite/datacollector/unixsignalhandler.h
new file mode 100644
index 0000000..63c5d53
--- /dev/null
+++ b/basicsuite/datacollector/unixsignalhandler.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 UNIXSIGNALHANDLER_H
+#define UNIXSIGNALHANDLER_H
+
+#include <QObject>
+
+class QSocketNotifier;
+
+class UnixSignalHandler : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit UnixSignalHandler(QObject *parent = nullptr);
+
+public:
+ static int setupSignalHandlers();
+ static void sigIntHandler(int);
+ static void sigHupHandler(int);
+ static void sigTermHandler(int);
+
+signals:
+ void sigInt();
+ void sigHup();
+ void sigTerm();
+
+private slots:
+ void handleSigInt();
+ void handleSigHup();
+ void handleSigTerm();
+
+private:
+ static int sigintFd[2];
+ static int sighupFd[2];
+ static int sigtermFd[2];
+
+ QSocketNotifier *m_sigintNotifier;
+ QSocketNotifier *m_sighupNotifier;
+ QSocketNotifier *m_sigtermNotifier;
+};
+
+#endif // UNIXSIGNALHANDLER_H
diff --git a/basicsuite/demos.xml b/basicsuite/demos.xml
index a465846..d0add1b 100644
--- a/basicsuite/demos.xml
+++ b/basicsuite/demos.xml
@@ -1,13 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<applications>
+<!--
<application title="About Qt for Device Creation" priority="10" location="/data/user/qt/about-b2qt" main="main.qml" icon="/data/user/qt/about-b2qt/preview_l.jpg">
<description>
Provides an introduction to what Qt for Device Creation is all about.
</description>
</application>
-<application title="Camera" location="/data/user/qt/camera" main="main.qml" icon="/data/user/qt/camera/preview_l.jpg" exclude="linux-beagleboard;linux-beaglebone;linux-raspberrypi;linux-emulator;linux-apalis-imx6;linux-colibri-vf">
+<application title="Camera" location="/data/user/qt/camera" main="main.qml" icon="/data/user/qt/camera/preview_l.jpg" exclude="linux-jetson-tx1;linux-beagleboard;linux-beaglebone;linux-raspberrypi;linux-emulator;linux-apalis-imx6;linux-colibri-vf">
<description>
This example demonstrates the use of the camera features of Qt Multimedia with Qt Quick.
@@ -20,6 +21,7 @@ Demo can be used to take photos which are saved in the /data/images/ directory.
The Planets example showcases Qt Canvas 3D. This demo combines WebGL code and three.js with QML and Qt Quick Controls.
</description>
</application>
+-->
<application title="Qt Charts - Gallery" location="/data/user/qt/enterprise-charts" main="main.qml" icon="/data/user/qt/enterprise-charts/preview_l.jpg">
<description>
@@ -29,6 +31,7 @@ Qt Charts is part of the Qt Enterprise addons package which provides a set of ea
</description>
</application>
+<!--
<application title="Qt Quick Extras Dashboard" location="/data/user/qt/enterprise-dashboard" main="main.qml" icon="/data/user/qt/enterprise-dashboard/preview_l.jpg">
<description>
The dashboard demo uses Qt Quick Extras to create a digital dashboard for a car.
@@ -46,8 +49,9 @@ The Flat Style Gallery example showcases the Qt Quick Controls. This demo combin
Shows how to make a 3D surface plot, displaying three layers from three different height map images using Surface3D with Qt Quick.
</description>
</application>
+-->
-<application title="Graphical Effects" location="/data/user/qt/graphicaleffects" main="main.qml" icon="/data/user/qt/graphicaleffects/preview_l.jpg" exclude="linux-beaglebone;linux-colibri-vf">
+<application title="Graphical Effects" location="/data/user/qt/graphicaleffects" main="main.qml" icon="/data/user/qt/graphicaleffects/preview_l.jpg" exclude="linux-beaglebone;linux-colibri-vf;linux-colibri-imx7;linux-imx7s-warp;linux-nitrogen7">
<description>
This example shows the Qt Quick ShaderEffect type and the Qt Graphical Effects module. Qt Quick provides the ability to use inline GLSL in your Qt Quick applications to create stunning visual effects.
@@ -63,6 +67,7 @@ It can play content either from a file or a network source, both videos and musi
</description>
</application>
+<!--
<application title="Qt5 Cinematic Demo" location="/data/user/qt/qt5-cinematicdemo" main="main.qml" icon="/data/user/qt/qt5-cinematicdemo/preview_l.jpg">
<description>
The Qt 5 Cinematic Experience is a demo by "QUIt Coding".
@@ -70,8 +75,9 @@ The Qt 5 Cinematic Experience is a demo by "QUIt Coding".
The demo shows off a number features of Qt Quick. A nicely styled list control of movie covers with lighting effects, particles and transitions. The information roll-down curvy curtain is implemented using inline GLSL in the QML file. The source code for this demo and more awesome looking Qt Quick examples are available from quitcoding.com.
</description>
</application>
+-->
-<application title="Web Browser" location="/data/user/qt/qtwebbrowser" main="main.qml" icon="/data/user/qt/qtwebbrowser/preview_l.jpg" exclude="linux-emulator;linux-raspberrypi;linux-beaglebone;linux-colibri-vf">
+<application title="Web Browser" location="/data/user/qt/qtwebbrowser" main="main.qml" icon="/data/user/qt/qtwebbrowser/preview_l.jpg" exclude="linux-emulator;linux-raspberrypi;linux-beaglebone;linux-colibri-vf;linux-colibri-imx7;linux-imx7s-warp;linux-nitrogen7">
<description>
This example demonstrates the use of Qt WebEngine with Qt Quick.
@@ -87,4 +93,20 @@ Qt for Device Creation comes with Qt Virtual Keyboard - a framework that consist
</description>
</application>
+<application title="E-Bike" location="/data/user/qt/ebike-ui" main="main.qml" icon="/data/user/qt/ebike-ui/preview_l.jpg">
+<description>
+An E-bike instrument cluster concept designed and implemented by Qt.
+
+The entire concept is a testament that Qt brings the HMI designers and Software engineers together by using the same tools, allowing them to fast prototyping through collaborative workflow. In addition to that, Qt is optimized for running on low end SoC even the ones that don't have GPU acceleration for graphics.
+</description>
+</application>
+
+<application title="Quick Controls 2" location="/data/user/qt/qtquickcontrols2" main="main.qml" icon="/data/user/qt/qtquickcontrols2/preview_l.jpg">
+<description>
+The gallery example is a simple application with a drawer menu that contains all the Qt Quick Controls 2.
+
+Each menu item opens a page that shows the graphical appearance of a control, allows you to interact with the control, and explains in which circumstances it is handy to use this control.
+</description>
+</application>
+
</applications>
diff --git a/basicsuite/ebike-ui/.gitignore b/basicsuite/ebike-ui/.gitignore
new file mode 100644
index 0000000..fab7372
--- /dev/null
+++ b/basicsuite/ebike-ui/.gitignore
@@ -0,0 +1,73 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+
diff --git a/basicsuite/ebike-ui/BikeInfoTab.qml b/basicsuite/ebike-ui/BikeInfoTab.qml
new file mode 100644
index 0000000..0dc4f27
--- /dev/null
+++ b/basicsuite/ebike-ui/BikeInfoTab.qml
@@ -0,0 +1,483 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import DataStore 1.0
+
+import "./BikeStyle"
+
+Item {
+ property string selectedComponent: "battery"
+ property string bikeImageSource: "images/bike-battery.png"
+ property string componentName: "Battery"
+ property string componentStatusImageSource: "images/ok.png"
+ property color statusLineColor: selectedComponent === "rearwheel" ? Colors.bikeInfoLineWarning : Colors.bikeInfoLineOk
+ property int statusLineLength: 280
+ property string primaryDetails: "Health"
+ property string primaryDetailsValue: "85%"
+ property bool primaryDetailsOk: true
+ property var activeComponent: batteryCircle
+
+ function updateComponent() {
+ if (selectedComponent === "")
+ bikeImageSource = "images/bike.png";
+ else
+ bikeImageSource = "images/bike-" + selectedComponent + ".png";
+
+ if (selectedComponent === "battery") {
+ componentName = "Battery";
+ primaryDetails = "Health";
+ primaryDetailsValue = "85%";
+ primaryDetailsOk = true;
+ statusLineLength = 280;
+ } else if (selectedComponent === "brakes") {
+ componentName = "Brakes";
+ primaryDetails = "Health";
+ primaryDetailsValue = "85%";
+ primaryDetailsOk = true;
+ statusLineLength = 448;
+ } else if (selectedComponent === "chain") {
+ componentName = "Chain";
+ primaryDetails = "Health";
+ primaryDetailsValue = "85%";
+ primaryDetailsOk = true;
+ statusLineLength = 0;
+ } else if (selectedComponent === "gears") {
+ componentName = "Gears";
+ primaryDetails = "Health";
+ primaryDetailsValue = "85%";
+ primaryDetailsOk = true;
+ statusLineLength = 245;
+ } else if (selectedComponent === "light") {
+ componentName = "Light";
+ primaryDetails = "Health";
+ primaryDetailsValue = "85%";
+ primaryDetailsOk = true;
+ statusLineLength = 300;
+ } else if (selectedComponent === "frontwheel") {
+ componentName = "Front wheel";
+ primaryDetails = "Tire pressure";
+ primaryDetailsValue = "6.8 bar / 100 psi";
+ primaryDetailsOk = true;
+ statusLineLength = 340;
+ } else if (selectedComponent === "rearwheel") {
+ componentName = "Rear wheel";
+ primaryDetails = "Tire pressure";
+ primaryDetailsValue = "4.0 bar / 58 psi";
+ primaryDetailsOk = false;
+ statusLineLength = 210;
+ }
+ }
+
+ Text {
+ id: bikeInfoText
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+ height: UILayout.configurationItemHeight
+ width: parent.width
+ text: qsTr("BIKE INFO")
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.configurationTitleSize
+ }
+ color: Colors.tabTitleColor
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ ColumnSpacer {
+ id: spacer
+ anchors.top: bikeInfoText.bottom
+ color: Colors.tabItemBorder
+ }
+
+
+ Image {
+ id: bikeImage
+ anchors {
+ top: spacer.bottom
+ right: parent.right
+ rightMargin: -30
+ }
+ source: bikeImageSource
+ }
+
+ Rectangle {
+ id: brakesCircle
+ width: 2 * UILayout.bikeInfoCircleRadius
+ height: 2 * UILayout.bikeInfoCircleRadius
+ radius: UILayout.bikeInfoCircleRadius
+ anchors {
+ verticalCenter: bikeImage.top
+ verticalCenterOffset: 43
+ horizontalCenter: bikeImage.left
+ horizontalCenterOffset: 243
+ }
+
+ color: "transparent"
+ border.width: UILayout.bikeInfoLineWidth
+ border.color: selectedComponent === "brakes" ? Colors.bikeInfoLineOk : Colors.bikeInfoDeselected
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ activeComponent = brakesCircle;
+ selectedComponent = "brakes"
+ updateComponent()
+ }
+ }
+ }
+
+ Rectangle {
+ id: lightCircle
+ width: 2 * UILayout.bikeInfoCircleRadius
+ height: 2 * UILayout.bikeInfoCircleRadius
+ radius: UILayout.bikeInfoCircleRadius
+ anchors {
+ verticalCenter: bikeImage.top
+ verticalCenterOffset: 77
+ horizontalCenter: bikeImage.left
+ horizontalCenterOffset: 252
+ }
+
+ color: "transparent"
+ border.width: UILayout.bikeInfoLineWidth
+ border.color: selectedComponent === "light" ? Colors.bikeInfoLineOk : Colors.bikeInfoDeselected
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ activeComponent = lightCircle;
+ selectedComponent = "light"
+ updateComponent()
+ }
+ }
+ }
+
+ Rectangle {
+ id: batteryCircle
+ width: 2 * UILayout.bikeInfoCircleRadius
+ height: 2 * UILayout.bikeInfoCircleRadius
+ radius: UILayout.bikeInfoCircleRadius
+ anchors {
+ verticalCenter: bikeImage.top
+ verticalCenterOffset: 106
+ horizontalCenter: bikeImage.left
+ horizontalCenterOffset: 200
+ }
+
+ color: "transparent"
+ border.width: UILayout.bikeInfoLineWidth
+ border.color: selectedComponent === "battery" ? Colors.bikeInfoLineOk : Colors.bikeInfoDeselected
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ activeComponent = batteryCircle;
+ selectedComponent = "battery"
+ updateComponent()
+ }
+ }
+ }
+
+ Rectangle {
+ id: gearsCircle
+ width: 2 * UILayout.bikeInfoCircleRadius
+ height: 2 * UILayout.bikeInfoCircleRadius
+ radius: UILayout.bikeInfoCircleRadius
+ anchors {
+ verticalCenter: bikeImage.top
+ verticalCenterOffset: 143
+ horizontalCenter: bikeImage.left
+ horizontalCenterOffset: 106
+ }
+
+ color: "transparent"
+ border.width: UILayout.bikeInfoLineWidth
+ border.color: selectedComponent === "gears" ? Colors.bikeInfoLineOk : Colors.bikeInfoDeselected
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ activeComponent = gearsCircle;
+ selectedComponent = "gears"
+ updateComponent()
+ }
+ }
+ }
+
+ Rectangle {
+ id: rearWheelCircle
+ width: 2 * UILayout.bikeInfoCircleRadius
+ height: 2 * UILayout.bikeInfoCircleRadius
+ radius: UILayout.bikeInfoCircleRadius
+ anchors {
+ verticalCenter: bikeImage.top
+ verticalCenterOffset: 144
+ horizontalCenter: bikeImage.left
+ horizontalCenterOffset: 56
+ }
+
+ color: "transparent"
+ border.width: UILayout.bikeInfoLineWidth
+ border.color: selectedComponent === "rearwheel" ? Colors.bikeInfoLineWarning : Colors.bikeInfoDeselected
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ activeComponent = rearWheelCircle;
+ selectedComponent = "rearwheel"
+ updateComponent()
+ }
+ }
+
+ Image {
+ anchors.centerIn: parent
+ source: "images/warning.png"
+ }
+ }
+
+ Rectangle {
+ id: frontWheelCircle
+ width: 2 * UILayout.bikeInfoCircleRadius
+ height: 2 * UILayout.bikeInfoCircleRadius
+ radius: UILayout.bikeInfoCircleRadius
+ anchors {
+ verticalCenter: bikeImage.top
+ verticalCenterOffset: 144
+ horizontalCenter: bikeImage.left
+ horizontalCenterOffset: 322
+ }
+
+ color: "transparent"
+ border.width: UILayout.bikeInfoLineWidth
+ border.color: selectedComponent === "frontwheel" ? Colors.bikeInfoLineOk : Colors.bikeInfoDeselected
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ activeComponent = frontWheelCircle;
+ selectedComponent = "frontwheel"
+ updateComponent()
+ }
+ }
+ }
+
+ Canvas {
+ id: slantedLine
+ anchors {
+ left: statusLineHorizontal.right
+ top: statusLineHorizontal.top
+ right: activeComponent.horizontalCenter
+ bottom: activeComponent.verticalCenter
+ }
+
+ onPaint: {
+ var ctx = getContext("2d");
+ ctx.reset();
+
+ // Calculate line length and subtract circle radius
+ var lineLength = Math.sqrt(slantedLine.width * slantedLine.width +
+ slantedLine.height * slantedLine.height);
+ lineLength -= UILayout.bikeInfoCircleRadius;
+
+ // Calculate angle
+ var angle = Math.atan2(slantedLine.height, slantedLine.width);
+
+ // Calculate new endpoints
+ var x = Math.cos(angle) * lineLength;
+ var y = Math.sin(angle) * lineLength;
+
+ ctx.lineCap = "round";
+ ctx.strokeStyle = statusLineColor;
+ ctx.lineWidth = UILayout.bikeInfoLineWidth;
+ ctx.beginPath();
+ ctx.moveTo(0, 1);
+ ctx.lineTo(x, y);
+ ctx.stroke();
+ }
+ }
+
+ Image {
+ id: componentStatusImage
+ anchors {
+ verticalCenter: spacer.bottom
+ verticalCenterOffset: (UILayout.bikeInfoComponentBaselineOffset + UILayout.bikeInfoComponentLineOffset) / 2
+ left: parent.left
+ }
+ source: componentStatusImageSource
+ visible: selectedComponent != ""
+ }
+
+ Text {
+ id: componentNameText
+ anchors {
+ baseline: spacer.bottom
+ baselineOffset: UILayout.bikeInfoComponentBaselineOffset
+ left: componentStatusImage.right
+ leftMargin: 5
+ }
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.bikeInfoComponentHeaderTextSize
+ }
+ color: Colors.bikeInfoComponentHeader
+ text: componentName
+ visible: selectedComponent != ""
+ }
+
+ // The line goes here somehow
+ Rectangle {
+ id: statusLineHorizontal
+ anchors {
+ top: componentNameText.baseline
+ topMargin: UILayout.bikeInfoComponentLineOffset
+ left: parent.left
+ }
+ height: UILayout.bikeInfoLineWidth
+ width: statusLineLength
+ color: statusLineColor
+ visible: selectedComponent != ""
+ }
+
+ Text {
+ id: primaryDetailsText
+ anchors {
+ baseline: statusLineHorizontal.bottom
+ baselineOffset: UILayout.bikeInfoLineDetailsMargin
+ left: parent.left
+ }
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.bikeInfoInfoHeaderTextSize
+ }
+ color: Colors.bikeInfoComponentText
+ text: primaryDetails
+ visible: selectedComponent != ""
+ }
+
+ Text {
+ id: primaryDetailsValueText
+ anchors {
+ baseline: primaryDetailsText.baseline
+ baselineOffset: UILayout.bikeInfoDetailsValueMargin
+ left: parent.left
+ }
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.bikeInfoInfoHeaderTextSize
+ }
+ color: primaryDetailsOk ? Colors.bikeInfoComponentOk : Colors.bikeInfoComponentWarning
+ text: primaryDetailsValue
+ visible: selectedComponent != ""
+ }
+
+ Text {
+ id: lastMaintenanceText
+ anchors {
+ baseline: primaryDetailsValueText.baseline
+ baselineOffset: UILayout.bikeInfoDetailsBaselineMargin
+ left: parent.left
+ }
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.bikeInfoInfoHeaderTextSize
+ }
+ color: Colors.bikeInfoComponentText
+ text: qsTr("Last maintenance")
+ visible: selectedComponent != ""
+ }
+
+ Text {
+ id: lastMaintenanceValueText
+ anchors {
+ baseline: lastMaintenanceText.baseline
+ baselineOffset: UILayout.bikeInfoDetailsValueMargin
+ left: parent.left
+ }
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.bikeInfoInfoHeaderTextSize
+ }
+ color: Colors.bikeInfoComponentOk
+ text: "10/3/2017"
+ visible: selectedComponent != ""
+ }
+
+ Text {
+ id: nextMaintenanceText
+ anchors {
+ baseline: lastMaintenanceValueText.baseline
+ baselineOffset: UILayout.bikeInfoDetailsBaselineMargin
+ left: parent.left
+ }
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.bikeInfoInfoHeaderTextSize
+ }
+ color: Colors.bikeInfoComponentText
+ text: qsTr("Scheduled maintenance")
+ visible: selectedComponent != ""
+ }
+
+ Text {
+ id: nextMaintenanceValueText
+ anchors {
+ baseline: nextMaintenanceText.baseline
+ baselineOffset: UILayout.bikeInfoDetailsValueMargin
+ left: parent.left
+ }
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.bikeInfoInfoHeaderTextSize
+ }
+ color: Colors.bikeInfoComponentOk
+ text: "10/3/2018"
+ visible: selectedComponent != ""
+ }
+}
diff --git a/basicsuite/ebike-ui/BikeStyle/Colors.qml b/basicsuite/ebike-ui/BikeStyle/Colors.qml
new file mode 100644
index 0000000..0086c7e
--- /dev/null
+++ b/basicsuite/ebike-ui/BikeStyle/Colors.qml
@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+pragma Singleton
+import QtQuick 2.9
+
+QtObject {
+ readonly property color clockText: "#d7dbe4"
+ readonly property color clockBackground: "#020611"
+ readonly property color mainBackground: "#1b1f2a"
+ readonly property color separator: "#040809"
+ readonly property color dottedRing: "#52576b"
+ readonly property color distanceText: "#ffffff"
+ readonly property color distanceUnit: "#acafbc"
+ readonly property color speedViewBackgroundCornered: "#020611"
+ readonly property color speedText: "#ffffff"
+ readonly property color speedUnit: "#acafbc"
+ readonly property color averageSpeedText: "#ffffff"
+ readonly property color averageSpeedUnit: "#acafbc"
+ readonly property color assistDistanceText: "#ffffff"
+ readonly property color assistDistanceUnit: "#acafbc"
+ readonly property color modeSelected: "#a0e63e"
+ readonly property color modeUnselected: "#acafbc"
+ readonly property color speedGradientStart: "#fcff2a"
+ readonly property color speedGradientEnd: "#41cd52"
+ readonly property color batteryGradientStart: "#34daea"
+ readonly property color batteryGradientEnd: "#3aeb95"
+ readonly property color assistPowerGradientStart: "#ffd200"
+ readonly property color assistPowerGradientEnd: "#f7971e"
+ readonly property color assistPowerEmpty: "#52576b"
+ readonly property color musicPlayerBackground: "#020611"
+ readonly property color musicPlayerSongText: "#d7dbe4"
+ readonly property color musicPlayerTimeText: "#989ba8"
+ readonly property color naviPageSuggestionBorder: "#a0e63e"
+ readonly property color naviPageSuggestionText: "#1b1f2a"
+ readonly property color naviPageSuggestionsDivider: "#989ba8"
+ readonly property color naviPageIconBackground: "#1b1f2a"
+ readonly property color naviPageIconPressedBackground: "#a0e63e"
+ readonly property color naviPageTripBackground: "#1b1f2a"
+ readonly property color naviPageTripDivider: "#acafbc"
+ readonly property color naviPageGuideBackground: "#1b1f2a"
+ readonly property color naviPageGuideTextColor: "#ffffff"
+ readonly property color naviPageGuideUnitColor: "#acafbc"
+ readonly property color naviPageGuideAddressColor: "#ffffff"
+ readonly property color curtainBackground: "#020611"
+ readonly property color tabBackground: "#020611"
+ readonly property color activeTabBorder: "#a0e63e"
+ readonly property color activeTabIcon: "#ffffff"
+ readonly property color tabIcon: "#989ba8"
+ readonly property color tabTitleColor: "#ffffff"
+ readonly property color tabItemColor: "#989ba8"
+ readonly property color tabItemBorder: "#1b1f2a"
+ readonly property color languageTextColor: "#acafbc"
+ readonly property color checkboxBorderColorChecked: "#a0e63e"
+ readonly property color checkboxBorderColor: "#acafbc"
+ readonly property color checkboxCheckedText: "#ffffff"
+ readonly property color checkboxUncheckedBackground: "#020611"
+ readonly property color sliderBackground: "#52576b"
+ readonly property color sliderInnerBackground: "#000000"
+ readonly property color sliderMinimumValue: "#fcff2a"
+ readonly property color sliderMaximumValue: "#41cd52"
+ readonly property color activeButtonBackground: "#a0e63e"
+ readonly property color inactiveButtonBackground: "#020611"
+ readonly property color activeButtonText: "#020611"
+ readonly property color inactiveButtonText: "#acafbc"
+ readonly property color inactiveButtonBorder: "#acafbc"
+ readonly property color switchOn: "#a0e63e"
+ readonly property color switchOff: "#ffffff"
+ readonly property color switchBackgroundOn: "#4da0e63e"
+ readonly property color switchBackgroundOff: "#52576b"
+ readonly property color bikeInfoDeselected: "#ffffff"
+ readonly property color bikeInfoLineOk: "#a0e63e"
+ readonly property color bikeInfoLineWarning: "#d4145a"
+ readonly property color bikeInfoComponentHeader: "#ffffff"
+ readonly property color bikeInfoComponentText: "#acafbc"
+ readonly property color bikeInfoComponentOk: "#ffffff"
+ readonly property color bikeInfoComponentWarning: "#d4145a"
+ readonly property color chartSpeed: "#a0e63e"
+ readonly property color chartAssistpower: "#34daea"
+ readonly property color chartLegend: "#ffffff"
+ readonly property color chartLabel: "#989ba8"
+ readonly property color chartTimeLabel: "#acafbc"
+ readonly property color chartGridLine: "#111520"
+ readonly property color statsButtonPressed: "#a0e63e"
+ readonly property color statsButtonInactive: "#52576b"
+ readonly property color statsButtonActive: "#848794"
+ readonly property color statsButtonInactiveText: "#52576b"
+ readonly property color statsButtonActiveText: "#ffffff"
+ readonly property color statsDescriptionText: "#989ba8"
+ readonly property color statsValueText: "#ffffff"
+ readonly property color statsSeparator: "#111520"
+}
diff --git a/basicsuite/ebike-ui/BikeStyle/UILayout.qml b/basicsuite/ebike-ui/BikeStyle/UILayout.qml
new file mode 100644
index 0000000..55f6baf
--- /dev/null
+++ b/basicsuite/ebike-ui/BikeStyle/UILayout.qml
@@ -0,0 +1,257 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+pragma Singleton
+import QtQuick 2.9
+
+QtObject {
+ readonly property int clockBaselineMargin: 25
+ readonly property int clockFontSize: 18
+
+ readonly property int statsIconTop: 56
+ readonly property int statsIconLeft: 50
+ readonly property int statsIconSeparator: 20
+ readonly property int statsIconWidth: 40
+ readonly property int statsIconHeight: 40
+ readonly property int statsTextSeparator: 15
+ readonly property int statsTextSize: 20
+ readonly property int statsTextTopOffset: 0
+ readonly property int statsUnitBaselineOffset: 0
+
+ readonly property int lightsIconBottom: 90
+ readonly property int lightsIconLeft: 50
+ readonly property int lightsIconWidth: 80
+ readonly property int lightsIconHeight: 80
+
+ readonly property int naviModeCenterMargin: 90
+
+ readonly property int naviIconTop: 45
+ readonly property int naviIconRight: 50
+ readonly property int naviIconWidth: 80
+ readonly property int naviIconHeight: 80
+ readonly property int naviTextSize: 26
+ readonly property int naviTextMargin: 30
+
+ readonly property int modeBottomOffset: 84
+ readonly property int modeDistance: 50
+ readonly property int modeTextSize: 20
+
+ readonly property int speedViewTop: 78
+ readonly property int speedViewRadius: 150 /* Normal mode */
+ readonly property int speedViewRadiusMinified: 90 /* Minified to corner mode */
+ readonly property int speedViewRadiusEnlarged: 205 /* Enlarged to full screen mode */
+ readonly property int speedViewDots: 96
+ readonly property int speedViewDotsMinified: 48
+ readonly property int speedViewDotsEnlarged: 128
+ readonly property int speedViewCornerLeftMargin: 15
+ readonly property int speedViewCornerBottomMargin: 15
+ readonly property int speedViewInnerRadius: 125
+ readonly property int speedViewInnerRadiusMinified: 65
+ readonly property int speedViewInnerRadiusEnlarged: 185
+ readonly property int speedViewInnerWidth: 12
+ readonly property int speedViewInnerWidthMinified: 8
+ readonly property double speedViewSpeedStart: Math.PI * 0.5 + Math.PI / 30
+ readonly property double speedViewSpeedEnd: Math.PI * 1.5 - Math.PI / 30
+ readonly property double speedViewBatteryStart: Math.PI * 0.5 - Math.PI / 30
+ readonly property double speedViewBatteryEnd: -Math.PI * 0.5 + Math.PI / 30
+ readonly property double speedViewAssistPowerStart: Math.PI * 0.5 + Math.PI / 34
+ readonly property double speedViewAssistPowerEnd: Math.PI * 0.5 - Math.PI / 34
+ readonly property int speedViewAssistPowerWidth: 6
+ readonly property int speedViewAssistPowerRadius: 230
+ readonly property int speedViewAssistPowerBottomOffset: 104
+ readonly property int speedBaselineOffset: 137
+ readonly property int speedBaselineOffsetMinified: 73
+ readonly property int speedBaselineOffsetEnlarged: 155
+ readonly property int speedTextSize: 108
+ readonly property int speedTextSizeMinified: 80
+ readonly property int speedTextSizeEnlarged: 190
+ readonly property int speedUnitsSize: 14
+ readonly property int speedUnitsSizeEnlarged: 18
+ readonly property int speedTextUnitMargin: 24
+ readonly property int speedTextUnitMarginMinified: 18
+ readonly property int speedTextUnitMarginEnlarged: 30
+ readonly property int speedIconsCenterOffset: 71
+ readonly property int speedIconsCenterOffsetEnlarged: 111
+ readonly property int speedInfoTextsOffsetEnlarged: -34
+ readonly property int speedInfoTextsSize: 38
+ readonly property int speedInfoTextsSizeEnlarged: 64
+ readonly property int speedInfoUnitsOffset: 24
+ readonly property int speedInfoUnitsOffsetEnlarged: 34
+ readonly property int averageSpeedIconMargin: -5
+ readonly property int averageSpeedIconWidth: 40
+ readonly property int averageSpeedIconHeight: 40
+ readonly property int assistDistanceIconMargin: -5
+ readonly property int assistDistanceIconWidth: 40
+ readonly property int assistDistanceIconHeight: 40
+ readonly property int assistPowerIconOffset: 49
+ readonly property int assistPowerIconOffsetEnlarged: 100
+ readonly property int assistPowerIconWidth: 30
+ readonly property int assistPowerIconHeight: 30
+ readonly property int assistPowerCircleRadius: 6
+ readonly property int assistPowerCircleOffset: 8
+ readonly property int assistPowerCircleVerticalOffset: 5
+ readonly property int assistPowerCircleTopMargin: 7
+ readonly property int speedometerCornerArrowWidth: 40
+ readonly property int speedometerCornerArrowHeight: 40
+ readonly property int ringValueText: 14
+
+ readonly property int musicPlayerWidth: 260
+ readonly property int musicPlayerHeight: 75
+ readonly property int musicPlayerCorner: 20
+ readonly property int musicPlayerIconWidth: 40
+ readonly property int musicPlayerIconHeight: 40
+ readonly property int musicPlayerIconBottom: 5
+ readonly property int musicPlayerIconSpacing: 50
+ readonly property int musicPlayerTextBottom: 5
+ readonly property int musicPlayerTextSize: 16
+
+ readonly property int naviPageLocationWidth: 300
+ readonly property int naviPageLocationHeight: 40
+ readonly property int naviPageLocationRadius: 20
+ readonly property int naviPageLocationTopMargin: 60
+ readonly property int naviPageLocationLeftPadding: 20
+ readonly property int naviPageIconBackgroundWidth: 50
+ readonly property int naviPageIconBackgroundHeight: 50
+ readonly property int naviPageIconBackgroundRadius: 25
+ readonly property int naviPageIconWidth: 40
+ readonly property int naviPageIconHeight: 40
+ readonly property int naviPageIconTopMargin: 15
+ readonly property int naviPageIconRightMargin: 15
+ readonly property int naviPageIconSpacing: 15
+ readonly property int naviPageSuggestionsOffset: 5
+ readonly property int naviPageSuggestionHeight: 40
+ readonly property int naviPageSuggestionTextSize: 16
+
+ readonly property int naviPageSearchIconWidth: 40
+ readonly property int naviPageSearchIconHeight: 40
+ readonly property int naviPageSearchIconMargin: 5
+ readonly property int naviPageSearchTextSize: 16
+
+ readonly property int naviPageTripWidth: 220
+ readonly property int naviPageTripHeight: 40
+ readonly property int naviPageTripRadius: 20
+ readonly property int naviPageTripDividerWidth: 2
+ readonly property int naviPageTripDividerHeight: 20
+ readonly property int naviPageTripBottomMargin: 15
+ readonly property int naviPageTripSearchMargin: 15
+ readonly property int naviPageTripTotalTextSize: 18
+ readonly property int naviPageTripTotalUnitSize: 18
+
+ readonly property int naviPageGuideRadius: 90
+ readonly property int naviPageGuideRightMargin: 15
+ readonly property int naviPageGuideBottomMargin: 15
+ readonly property int naviPageGuideArrowTopMargin: 30
+ readonly property int naviPageGuideArrowLeftMargin: 50
+ readonly property int naviPageGuideArrowWidth: 80
+ readonly property int naviPageGuideArrowHeight: 80
+ readonly property int naviPageGuideAddressBaselineMargin: 20
+ readonly property int naviPageGuideAddressRightMargin: 20
+ readonly property int naviPageGuideAddressTextSize: 14
+ readonly property int naviPageGuideDistanceBaselineMargin: 20
+ readonly property int naviPageGuideDistanceTextSize: 26
+ readonly property int naviPageGuideUnitTextSize: 26
+
+ readonly property int tabBarTabHeight: 60
+ readonly property int tabBarFontSize: 24
+ readonly property int tabButtonTopMargin: 13
+ readonly property int tabButtonIconWidth: 40
+ readonly property int tabButtonIconHeight: 40
+ readonly property int curtainMargin: 30
+ readonly property int curtainCloseHeight: 30
+ readonly property int configurationItemHeight: 59
+ readonly property int configurationItemSeparator: 1
+ readonly property int configurationTextSize: 18
+ readonly property int configurationTitleSize: 18
+ readonly property int languageTextSize: 18
+ readonly property int checkboxWidth: 20
+ readonly property int checkboxHeight: 20
+ readonly property int checkboxRadius: 5
+ readonly property int checkboxLabelSize: 16
+ readonly property int checkboxTextOffset: 10
+ readonly property int checkboxSliderOffset: 20
+ readonly property int sliderHandleRadius: 10
+ readonly property int sliderHandleRadiusInner: 6
+ readonly property int sliderWidth: 256
+ readonly property int sliderHeight: 4
+ readonly property int switchWidth: 50
+ readonly property int switchHeight: 20
+ readonly property int switchIndicatorRadius: 15
+ readonly property int unitButtonWidthMargin: 20
+ readonly property int unitButtonHeight: 40
+ readonly property int unitButtonSpacing: 10
+ readonly property int unitFontSize: 16
+ readonly property int bikeInfoComponentBaselineOffset: 30
+ readonly property int bikeInfoComponentLineOffset: 14
+ readonly property int bikeInfoLineWidth: 2
+ readonly property int bikeInfoLineDetailsMargin: 24
+ readonly property int bikeInfoDetailsValueMargin: 20
+ readonly property int bikeInfoDetailsBaselineMargin: 30
+ readonly property int bikeInfoComponentHeaderTextSize: 18
+ readonly property int bikeInfoInfoHeaderTextSize: 18
+ readonly property int bikeInfoCircleRadius: 17
+
+ readonly property int statsTripButtonWidth: 40
+ readonly property int statsTripButtonHeight: 40
+ readonly property int statsTripButtonMarginSide: 30
+ readonly property int statsTripButtonMarginTop: 60
+ readonly property int statsEndtripWidth: 150
+ readonly property int statsEndtripHeight: 40
+ readonly property int statsEndtripMargin: 60
+ readonly property int statsEndtripTextSize: 16
+ readonly property int statsDescriptionTextSize: 18
+ readonly property int statsValueTextSize: 18
+ readonly property int statsOdometerMarginRight: 30
+ readonly property int statsOdometerBaselineOffset: 40
+ readonly property int statsTopMargin: 28
+ readonly property int statsHeight: 39
+ readonly property int statsCenterOffset: 30
+ readonly property int chartWidth: 440
+ readonly property int chartHeight: 200
+ readonly property int chartBottomMargin: 0
+ readonly property int chartRightMargin: 0
+ readonly property int chartLegendTextSize: 14
+ readonly property int chartTimeLabelSize: 14
+ readonly property int chartSpeedLabelSize: 14
+ readonly property int chartAssistpowerLabelSize: 14
+
+ readonly property int topViewHeight: 229
+ readonly property int bottomViewHeight: 251
+ readonly property int horizontalViewSeparatorHeight: 1
+ readonly property int horizontalViewSeparatorWidth: 170
+ readonly property int verticalViewSeparatorHeightTop: 110
+ readonly property int verticalViewSeparatorHeightBottom: 132
+ readonly property int verticalViewSeparatorWidth: 1
+}
diff --git a/basicsuite/ebike-ui/BikeStyle/qmldir b/basicsuite/ebike-ui/BikeStyle/qmldir
new file mode 100644
index 0000000..e8e9d2f
--- /dev/null
+++ b/basicsuite/ebike-ui/BikeStyle/qmldir
@@ -0,0 +1,2 @@
+singleton Colors 1.0 Colors.qml
+singleton UILayout 1.0 UILayout.qml
diff --git a/basicsuite/ebike-ui/ClockView.qml b/basicsuite/ebike-ui/ClockView.qml
new file mode 100644
index 0000000..21310c0
--- /dev/null
+++ b/basicsuite/ebike-ui/ClockView.qml
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import "./BikeStyle"
+// Permanent placeholder for time display
+Item {
+ width: backgroundImage.width
+ height: backgroundImage.height
+ z: 1
+
+ // Timer that will show the current time at the top of the screen
+ Timer {
+ interval: 500; running: true; repeat: true
+ onTriggered: timeLabel.text = new Date().toLocaleTimeString(Qt.locale("en_US"), Locale.ShortFormat)
+ }
+
+ Image {
+ id: backgroundImage
+ source: "images/top_curtain_drag.png"
+ anchors.centerIn: parent
+ }
+
+ Text {
+ id: timeLabel
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ baseline: parent.top
+ baselineOffset: UILayout.clockBaselineMargin
+ }
+ color: Colors.clockText
+ text: "--:--"
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.clockFontSize
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/ColumnSpacer.qml b/basicsuite/ebike-ui/ColumnSpacer.qml
new file mode 100644
index 0000000..2cdfe98
--- /dev/null
+++ b/basicsuite/ebike-ui/ColumnSpacer.qml
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+import "./BikeStyle"
+
+Rectangle {
+ height: UILayout.configurationItemSeparator
+ width: parent.width
+ color: Colors.tabItemBorder
+}
diff --git a/basicsuite/ebike-ui/ConfigurationDrawer.qml b/basicsuite/ebike-ui/ConfigurationDrawer.qml
new file mode 100644
index 0000000..258a264
--- /dev/null
+++ b/basicsuite/ebike-ui/ConfigurationDrawer.qml
@@ -0,0 +1,147 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "./BikeStyle"
+
+Drawer {
+ property alias bikeInfoTab: bikeInfoTab
+ property alias generalTab: generalTab
+ property alias viewTab: viewTab
+
+ background: Rectangle {
+ color: Colors.curtainBackground
+ width: parent.width
+ height: parent.height
+ }
+
+ TabBar {
+ id: bar
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: UILayout.curtainMargin
+ rightMargin: UILayout.curtainMargin
+ }
+ height: UILayout.tabBarTabHeight
+ background: Rectangle {
+ color: Colors.curtainBackground
+ }
+
+ IconifiedTabButton {
+ id: bikeInfoTabButton
+ height: parent.height
+ bar: bar
+ deselectedIcon: "images/info.png"
+ selectedIcon: "images/info_selected.png"
+ }
+
+ IconifiedTabButton {
+ id: configurationTabButton
+ height: parent.height
+ bar: bar
+ deselectedIcon: "images/settings.png"
+ selectedIcon: "images/settings_selected.png"
+ }
+
+ IconifiedTabButton {
+ id: viewTabButton
+ height: parent.height
+ bar: bar
+ deselectedIcon: "images/list.png"
+ selectedIcon: "images/list_selected.png"
+ }
+ }
+
+ StackLayout {
+ id: stackLayout
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: bar.bottom
+ leftMargin: UILayout.curtainMargin
+ rightMargin: UILayout.curtainMargin
+ }
+ height: 290
+ currentIndex: bar.currentIndex
+
+ BikeInfoTab {
+ id: bikeInfoTab
+ }
+
+ GeneralTab {
+ id: generalTab
+ }
+
+ ViewTab {
+ id: viewTab
+
+ onResetDemo: {
+ // Reset trip data
+ datastore.resetDemo()
+ // Reset navigation
+ naviPage.resetDemo()
+ }
+ }
+ }
+
+ Rectangle {
+ id: drawerClose
+ anchors {
+ top: stackLayout.bottom
+ left: parent.left
+ right: parent.right
+ }
+
+ width: parent.width
+ height: drawerCloseImage.implicitHeight
+ color: "transparent"
+
+ Image {
+ id: drawerCloseImage
+ source: "images/curtain_shadow_handle.png"
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: drawer.close()
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/ConfigurationItem.qml b/basicsuite/ebike-ui/ConfigurationItem.qml
new file mode 100644
index 0000000..350e910
--- /dev/null
+++ b/basicsuite/ebike-ui/ConfigurationItem.qml
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+
+import "./BikeStyle"
+
+Rectangle {
+ property string description
+
+ color: "transparent"
+ height: UILayout.configurationItemHeight
+ width: parent.width
+
+ Text {
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ left: parent.left
+ }
+ text: description
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.configurationTextSize
+ }
+ color: Colors.tabItemColor
+ verticalAlignment: Text.AlignVCenter
+ }
+}
diff --git a/basicsuite/ebike-ui/FpsItem.qml b/basicsuite/ebike-ui/FpsItem.qml
new file mode 100644
index 0000000..0ab183d
--- /dev/null
+++ b/basicsuite/ebike-ui/FpsItem.qml
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+Item {
+ id: fpsItem
+ property int frameCounter: 0
+ property int fpsValue: 0;
+
+ width: spinnerImage.width + fpsText.width
+ height: 48
+ z: 1
+
+ Image {
+ id: spinnerImage
+ source: "images/fps_icon.png"
+ NumberAnimation on rotation {
+ from: 0
+ to: 360
+ duration: 800
+ loops: Animation.Infinite
+ }
+ onRotationChanged: frameCounter++;
+ }
+
+ Text {
+ id: fpsText
+ anchors.right: parent.right
+ anchors.verticalCenter: spinnerImage.verticalCenter
+ color: "red"
+ text: "Fps: " + fpsItem.fpsValue
+ }
+
+ Timer {
+ interval: 2000
+ repeat: true
+ running: true
+ onTriggered: {
+ fpsValue = frameCounter / 2;
+ frameCounter = 0;
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/GeneralTab.qml b/basicsuite/ebike-ui/GeneralTab.qml
new file mode 100644
index 0000000..697a451
--- /dev/null
+++ b/basicsuite/ebike-ui/GeneralTab.qml
@@ -0,0 +1,279 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import DataStore 1.0
+
+import "./BikeStyle"
+
+Item {
+ Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Text {
+ height: UILayout.configurationItemHeight
+ width: parent.width
+ text: qsTr("GENERAL")
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.configurationTitleSize
+ }
+ color: Colors.tabTitleColor
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ ColumnSpacer {
+ color: Colors.tabItemBorder
+ }
+
+ ConfigurationItem {
+ description: qsTr("Language")
+
+ Text {
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ right: parent.right
+ }
+ text: qsTr("English")
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.languageTextSize
+ }
+ color: Colors.languageTextColor
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ ColumnSpacer {
+ color: Colors.tabItemBorder
+ }
+
+ ConfigurationItem {
+ description: qsTr("Brightness")
+
+ Text {
+ anchors {
+ right: autoBrightness.left
+ rightMargin: UILayout.checkboxTextOffset
+ verticalCenter: autoBrightness.verticalCenter
+ }
+ text: qsTr("Auto")
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.checkboxLabelSize
+ }
+ color: Colors.checkboxCheckedText
+ }
+
+ CheckBox {
+ id: autoBrightness
+ width: UILayout.checkboxWidth
+ anchors {
+ right: brightnessSlider.left
+ rightMargin: UILayout.checkboxSliderOffset
+ verticalCenter: brightnessSlider.verticalCenter
+ }
+ checked: brightness.automatic
+
+ indicator: Rectangle {
+ implicitWidth: UILayout.checkboxWidth
+ implicitHeight: UILayout.checkboxHeight
+ y: parent.height / 2 - height / 2
+ radius: UILayout.checkboxRadius
+ color: Colors.checkboxUncheckedBackground
+ border.color: autoBrightness.checked ? Colors.checkboxBorderColorChecked : Colors.checkboxBorderColor
+ border.width: autoBrightness.checked ? 2 : 1
+
+ Image {
+ source: "images/checkmark.png"
+ anchors.centerIn: parent
+ visible: autoBrightness.checked
+ }
+ }
+
+ contentItem: Item {}
+
+ onToggled: brightness.automatic = checked
+ }
+
+ Slider {
+ id: brightnessSlider
+ value: 4
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ right: parent.right
+ }
+ from: 6
+ to: 1
+ stepSize: -1
+ snapMode: Slider.SnapAlways
+ onMoved: brightness.brightness = value
+
+ background: Rectangle {
+ x: brightnessSlider.leftPadding
+ y: brightnessSlider.topPadding + brightnessSlider.availableHeight / 2 - height / 2
+ implicitWidth: UILayout.sliderWidth
+ implicitHeight: UILayout.sliderHeight
+ width: brightnessSlider.availableWidth
+ height: implicitHeight
+ radius: UILayout.sliderHeight / 2
+ color: Colors.sliderBackground
+
+ Rectangle {
+ // Since gradient is only available vertically, we must draw and rotate
+ width: parent.height
+ height: brightnessSlider.visualPosition * parent.width
+ radius: UILayout.sliderHeight / 2
+ gradient: Gradient {
+ GradientStop { position: 0; color: Colors.sliderMinimumValue }
+ GradientStop { position: 1; color: Colors.sliderMaximumValue }
+ }
+
+ transform: Rotation { origin.x: 0; origin.y: UILayout.sliderHeight; angle: -90}
+ }
+ }
+
+ handle: Rectangle {
+ x: brightnessSlider.leftPadding + brightnessSlider.visualPosition * (brightnessSlider.availableWidth - width)
+ y: brightnessSlider.topPadding + brightnessSlider.availableHeight / 2 - height / 2
+ implicitWidth: 2 * UILayout.sliderHandleRadius
+ implicitHeight: 2 * UILayout.sliderHandleRadius
+ radius: UILayout.sliderHandleRadius
+ color: Colors.sliderMaximumValue
+
+ Rectangle {
+ anchors.centerIn: parent
+ implicitWidth: 2 * UILayout.sliderHandleRadiusInner
+ implicitHeight: 2 * UILayout.sliderHandleRadiusInner
+ radius: UILayout.sliderHandleRadiusInner
+ color: Colors.sliderInnerBackground
+ }
+ }
+ }
+ }
+
+ ColumnSpacer {
+ color: Colors.tabItemBorder
+ }
+
+ ConfigurationItem {
+ description: qsTr("Units")
+
+ RoundButton {
+ id: kmhButton
+ width: UILayout.unitButtonWidthMargin * 2 + kmhText.implicitWidth
+ height: UILayout.unitButtonHeight
+ radius: height / 2
+ anchors {
+ verticalCenter: parent.verticalCenter
+ right: parent.right
+ }
+
+ background: Rectangle {
+ width: parent.width
+ height: parent.height
+ radius: parent.radius
+ color: datastore.unit === DataStore.Kmh ? Colors.activeButtonBackground : Colors.inactiveButtonBackground
+ border.color: Colors.inactiveButtonBorder
+ border.width: datastore.unit === DataStore.Kmh ? 0 : 1
+ }
+
+ contentItem: Text {
+ id: kmhText
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.unitFontSize
+ }
+ text: "km/h"
+ color: datastore.unit === DataStore.Kmh ? Colors.activeButtonText : Colors.inactiveButtonText
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ onClicked: datastore.unit = DataStore.Kmh
+ }
+
+ RoundButton {
+ id: mphButton
+ width: UILayout.unitButtonWidthMargin * 2 + mphText.implicitWidth
+ height: UILayout.unitButtonHeight
+ radius: height / 2
+ anchors {
+ verticalCenter: parent.verticalCenter
+ right: kmhButton.left
+ rightMargin: UILayout.unitButtonSpacing
+ }
+
+ background: Rectangle {
+ width: parent.width
+ height: parent.height
+ radius: parent.radius
+ color: datastore.unit === DataStore.Mph ? Colors.activeButtonBackground : Colors.inactiveButtonBackground
+ border.color: Colors.inactiveButtonBorder
+ border.width: datastore.unit === DataStore.Mph ? 0 : 1
+ }
+
+ contentItem: Text {
+ id: mphText
+ text: "mph"
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.unitFontSize
+ }
+ color: datastore.unit === DataStore.Mph ? Colors.activeButtonText : Colors.inactiveButtonText
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ onClicked: datastore.unit = DataStore.Mph
+ }
+ }
+
+ ColumnSpacer {
+ color: Colors.tabItemBorder
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/IconifiedTabButton.qml b/basicsuite/ebike-ui/IconifiedTabButton.qml
new file mode 100644
index 0000000..572ce51
--- /dev/null
+++ b/basicsuite/ebike-ui/IconifiedTabButton.qml
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+
+import "./BikeStyle"
+
+TabButton {
+ property string deselectedIcon
+ property string selectedIcon
+ property var bar
+
+ contentItem: Image {
+ width: UILayout.tabButtonIconWidth
+ height: UILayout.tabButtonIconHeight
+ source: bar.currentItem === parent ? selectedIcon : deselectedIcon
+ fillMode: Image.Pad
+ anchors {
+ top: parent.top
+ topMargin: UILayout.tabButtonTopMargin
+ horizontalCenter: parent.horizontalCenter
+ }
+ }
+
+ background: Rectangle {
+ color: Colors.tabBackground
+ height: parent.height
+
+ Rectangle {
+ visible: bar.currentItem === parent.parent
+ width: parent.width
+ height: 2
+ anchors.bottom: parent.bottom
+ color: Colors.activeTabBorder
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/LightsBox.qml b/basicsuite/ebike-ui/LightsBox.qml
new file mode 100644
index 0000000..327c819
--- /dev/null
+++ b/basicsuite/ebike-ui/LightsBox.qml
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Extras 1.4
+
+import "./BikeStyle"
+
+// Bottom-left corner, controls
+Item {
+ width: 320
+ height: UILayout.bottomViewHeight
+
+ Image {
+ id: lightsIcon
+ width: UILayout.lightsIconWidth
+ height: UILayout.lightsIconHeight
+ source: datastore.lights ? "images/lights_on.png" : "images/lights_off.png"
+ fillMode: Image.PreserveAspectFit
+ anchors {
+ left: parent.left
+ leftMargin: UILayout.lightsIconLeft
+ bottom: parent.bottom
+ bottomMargin: UILayout.lightsIconBottom
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: datastore.lights = !datastore.lights
+ }
+
+ Rectangle {
+ width: UILayout.horizontalViewSeparatorWidth
+ height: UILayout.horizontalViewSeparatorHeight
+ anchors.top: parent.top
+ anchors.left: parent.left
+ color: Colors.separator
+ }
+
+ Rectangle {
+ width: UILayout.verticalViewSeparatorWidth
+ height: UILayout.verticalViewSeparatorHeightBottom
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ color: Colors.separator
+ }
+}
diff --git a/basicsuite/ebike-ui/MainPage.qml b/basicsuite/ebike-ui/MainPage.qml
new file mode 100644
index 0000000..ae36401
--- /dev/null
+++ b/basicsuite/ebike-ui/MainPage.qml
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+import QtQuick.Extras 1.4
+
+import "./BikeStyle"
+
+Page {
+ background: Rectangle {
+ color: Colors.mainBackground
+ }
+ property alias statsButton: statsButton
+ property alias naviButton: naviButton
+ property alias lightsButton: lightsButton
+ property alias modeButton: modeButton
+ property string naviGuideArrowSource
+ property string naviGuideDistance
+ property string naviGuideAddress
+
+ StatsBox {
+ id: statsButton
+ anchors.left: parent.left
+ anchors.top: parent.top
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ swipeView.currentIndex = 0
+ }
+ }
+ }
+
+ NaviBox {
+ id: naviButton
+ anchors.right: parent.right
+ anchors.top: parent.top
+ arrowSource: naviGuideArrowSource
+ distance: naviGuideDistance
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ swipeView.currentIndex = 2
+ }
+ }
+ }
+
+ LightsBox {
+ id: lightsButton
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ }
+
+ ModeBox {
+ id: modeButton
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ }
+}
diff --git a/basicsuite/ebike-ui/ModeBox.qml b/basicsuite/ebike-ui/ModeBox.qml
new file mode 100644
index 0000000..ad4eba7
--- /dev/null
+++ b/basicsuite/ebike-ui/ModeBox.qml
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Extras 1.4
+import DataStore 1.0
+
+import "./BikeStyle"
+
+// Bottom-right corner, mode
+Item {
+ width: 320
+ height: UILayout.bottomViewHeight
+
+ Text {
+ id: sportModeText
+ anchors {
+ baseline: parent.bottom
+ baselineOffset: -UILayout.modeBottomOffset
+ horizontalCenter: parent.right
+ horizontalCenterOffset: -UILayout.naviModeCenterMargin
+ }
+ color: datastore.mode == DataStore.Sport ? Colors.modeSelected : Colors.modeUnselected
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.modeTextSize
+ }
+ text: qsTr("SPORT")
+ }
+
+ Text {
+ id: cruiseModeText
+ anchors {
+ baseline: sportModeText.baseline
+ baselineOffset: -UILayout.modeDistance
+ horizontalCenter: sportModeText.horizontalCenter
+ }
+ color: datastore.mode == DataStore.Cruise ? Colors.modeSelected : Colors.modeUnselected
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.modeTextSize
+ }
+ text: qsTr("CRUISE")
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: datastore.toggleMode()
+ }
+
+ Rectangle {
+ width: UILayout.horizontalViewSeparatorWidth
+ height: UILayout.horizontalViewSeparatorHeight
+ anchors.top: parent.top
+ anchors.right: parent.right
+ color: Colors.separator
+ }
+
+ Rectangle {
+ width: UILayout.verticalViewSeparatorWidth
+ height: UILayout.verticalViewSeparatorHeightBottom
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ color: Colors.separator
+ }
+}
diff --git a/basicsuite/ebike-ui/MusicPlayer.qml b/basicsuite/ebike-ui/MusicPlayer.qml
new file mode 100644
index 0000000..9d44534
--- /dev/null
+++ b/basicsuite/ebike-ui/MusicPlayer.qml
@@ -0,0 +1,239 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+
+import "./BikeStyle"
+
+Rectangle {
+ id: musicPlayer
+ width: UILayout.musicPlayerWidth
+ height: UILayout.musicPlayerHeight + UILayout.musicPlayerCorner
+ radius: UILayout.musicPlayerCorner
+ color: Colors.musicPlayerBackground
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ bottom: parent.bottom
+ bottomMargin: -UILayout.musicPlayerCorner
+ }
+ state: "hidden"
+ property bool isPlaying: false
+ property var songList: [
+ ["Post Malone - Rockstar", 218],
+ ["Ed Sheeran - Perfect", 263],
+ ["Imagine Dragons - Thunder", 187]
+ ]
+ property int currentSong: 0
+
+ function setupSong() {
+ var dur = songList[currentSong][1];
+ timeAnimation.stop();
+ songTimeText.songDuration = dur;
+ timeAnimation.from = dur;
+ timeAnimation.to = 0;
+ timeAnimation.duration = dur * 1000;
+ if (isPlaying)
+ timeAnimation.start();
+ }
+
+ function previousSong() {
+ if (currentSong === 0)
+ currentSong = songList.length - 1;
+ else
+ currentSong -= 1;
+ setupSong();
+ }
+
+ function nextSong() {
+ if (currentSong >= (songList.length - 1))
+ currentSong = 0;
+ else
+ currentSong += 1;
+ setupSong();
+ }
+
+ Component.onCompleted: setupSong()
+
+ Image {
+ id: playIcon
+ width: UILayout.musicPlayerIconWidth
+ height: UILayout.musicPlayerIconHeight
+ source: isPlaying
+ ? (playIconArea.pressed ? "images/pause_pressed.png" : "images/pause.png")
+ : (playIconArea.pressed ? "images/play_pressed.png" : "images/play.png")
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ bottom: parent.bottom
+ bottomMargin: UILayout.musicPlayerIconBottom + UILayout.musicPlayerCorner
+ }
+
+ MouseArea {
+ id: playIconArea
+ anchors {
+ fill: parent
+ margins: -UILayout.musicPlayerIconSpacing / 2
+ }
+ onClicked: {
+ isPlaying = !isPlaying
+ if (isPlaying) {
+ if (timeAnimation.running)
+ timeAnimation.resume()
+ else
+ timeAnimation.start()
+ } else
+ timeAnimation.pause()
+ }
+ }
+ }
+
+ Image {
+ id: previousIcon
+ width: UILayout.musicPlayerIconWidth
+ height: UILayout.musicPlayerIconHeight
+ source: previousIconArea.pressed ? "images/prevsong_pressed.png" : "images/prevsong.png"
+ anchors {
+ right: playIcon.left
+ rightMargin: UILayout.musicPlayerIconSpacing
+ bottom: playIcon.bottom
+ }
+
+ MouseArea {
+ id: previousIconArea
+ anchors {
+ fill: parent
+ margins: -UILayout.musicPlayerIconSpacing / 2
+ }
+ onClicked: previousSong()
+ }
+ }
+
+ Image {
+ id: nextIcon
+ width: UILayout.musicPlayerIconWidth
+ height: UILayout.musicPlayerIconHeight
+ source: nextIconArea.pressed ? "images/nextsong_pressed.png" : "images/nextsong.png"
+ anchors {
+ left: playIcon.right
+ leftMargin: UILayout.musicPlayerIconSpacing
+ bottom: playIcon.bottom
+ }
+
+ MouseArea {
+ id: nextIconArea
+ anchors {
+ fill: parent
+ margins: -UILayout.musicPlayerIconSpacing / 2
+ }
+ onClicked: nextSong()
+ }
+ }
+
+ Text {
+ id: songTitleText
+ anchors {
+ left: previousIcon.left
+ right: nextIcon.right
+ rightMargin: songTimeText.width + 5
+ baseline: previousIcon.top
+ baselineOffset: -UILayout.musicPlayerTextBottom
+ }
+
+ color: Colors.musicPlayerSongText
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.musicPlayerTextSize
+ }
+ text: songList[currentSong][0]
+ elide: Text.ElideRight
+ }
+
+ // Function for pretty-printing duration
+ function splitDuration(duration) {
+ var minutes = Math.floor(duration / 60);
+ var seconds = Math.floor(duration % 60);
+ if (seconds < 10)
+ seconds = "0" + seconds;
+ return minutes + ":" + seconds;
+ }
+
+ Text {
+ property int songDuration
+ id: songTimeText
+ anchors {
+ right: nextIcon.right
+ baseline: nextIcon.top
+ baselineOffset: -UILayout.musicPlayerTextBottom
+ }
+
+ color: Colors.musicPlayerTimeText
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.musicPlayerTextSize
+ }
+ text: splitDuration(songDuration)
+
+ NumberAnimation {
+ id: timeAnimation
+ target: songTimeText
+ property: "songDuration"
+ onStopped: {
+ if (isPlaying)
+ nextSong();
+ }
+ }
+ }
+
+ states: State {
+ name: "hidden"
+ PropertyChanges {
+ target: musicPlayer
+ anchors.bottomMargin: -musicPlayer.height
+ }
+ }
+
+ transitions: Transition {
+ from: ""
+ to: "hidden"
+ reversible: true
+ NumberAnimation {
+ properties: "anchors.bottomMargin"
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/NaviBox.qml b/basicsuite/ebike-ui/NaviBox.qml
new file mode 100644
index 0000000..53b55c8
--- /dev/null
+++ b/basicsuite/ebike-ui/NaviBox.qml
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+
+import "./BikeStyle"
+
+// Top-right corner, navi
+Item {
+ width: 320
+ height: UILayout.topViewHeight
+ property string arrowSource: "images/nav_right.png"
+ property string distance: "0"
+ property string unit: "m"
+
+ Image {
+ id: naviIcon
+ width: UILayout.naviIconWidth
+ height: UILayout.naviIconHeight
+ source: arrowSource
+ anchors {
+ top: parent.top
+ topMargin: UILayout.naviIconTop
+ horizontalCenter: parent.right
+ horizontalCenterOffset: -UILayout.naviModeCenterMargin
+ }
+ }
+
+ Item {
+ id: container
+ anchors.horizontalCenter: naviIcon.horizontalCenter
+ anchors.top: naviIcon.bottom
+ height: 30
+ width: naviText.width + 5 + naviUnit.width
+ visible: navigation.active
+
+ Text {
+ id: naviText
+ anchors.baseline: container.bottom
+ color: Colors.distanceText
+ font {
+ family: "Montserrat, Bold"
+ weight: Font.Bold
+ pixelSize: UILayout.naviTextSize
+ }
+ text: Math.round(datastore.convertSmallDistance(distance) / 10) * 10
+ }
+
+ Text {
+ id: naviUnit
+ anchors {
+ baseline: container.bottom
+ left: naviText.right
+ leftMargin: 5
+ }
+ color: Colors.distanceUnit
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.naviTextSize
+ }
+ text: datastore.smallUnit
+ }
+ }
+
+ Text {
+ id: navigateText
+ anchors.horizontalCenter: naviIcon.horizontalCenter
+ anchors.top: naviIcon.bottom
+ visible: !navigation.active
+ color: Colors.modeUnselected
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.modeTextSize
+ }
+ text: qsTr("NAVIGATE")
+ }
+
+ Rectangle {
+ width: UILayout.horizontalViewSeparatorWidth
+ height: UILayout.horizontalViewSeparatorHeight
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ color: Colors.separator
+ }
+
+ Rectangle {
+ width: UILayout.verticalViewSeparatorWidth
+ height: UILayout.verticalViewSeparatorHeightTop
+ anchors.top: parent.top
+ anchors.left: parent.left
+ color: Colors.separator
+ }
+}
diff --git a/basicsuite/ebike-ui/NaviButton.qml b/basicsuite/ebike-ui/NaviButton.qml
new file mode 100644
index 0000000..a34d540
--- /dev/null
+++ b/basicsuite/ebike-ui/NaviButton.qml
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+
+import "./BikeStyle"
+
+RoundButton {
+ property string iconSource
+
+ width: UILayout.naviPageIconBackgroundWidth
+ height: UILayout.naviPageIconBackgroundHeight
+ radius: UILayout.naviPageIconBackgroundRadius
+ z: 1
+
+ background: Item {
+ id: naviButtonBackground
+ width: parent.width
+ height: parent.height
+
+ Image {
+ id: naviButtonShadow
+ fillMode: Image.Pad
+ anchors {
+ horizontalCenter: naviButtonBackground.horizontalCenter
+ verticalCenter: naviButtonBackground.verticalCenter
+ horizontalCenterOffset: 1
+ verticalCenterOffset: 1
+ }
+ source: "images/map_btn_shadow.png"
+ }
+
+ Rectangle {
+ width: UILayout.naviPageIconBackgroundWidth
+ height: UILayout.naviPageIconBackgroundHeight
+ radius: UILayout.naviPageIconBackgroundRadius
+ color: parent.parent.down ? Colors.naviPageIconPressedBackground : Colors.naviPageIconBackground
+ }
+ }
+
+ contentItem: Item {}
+ Image {
+ anchors.centerIn: parent
+ width: UILayout.naviPageIconWidth
+ height: UILayout.naviPageIconHeight
+ source: iconSource
+ z: 3
+ }
+}
diff --git a/basicsuite/ebike-ui/NaviGuide.qml b/basicsuite/ebike-ui/NaviGuide.qml
new file mode 100644
index 0000000..1ba6f5e
--- /dev/null
+++ b/basicsuite/ebike-ui/NaviGuide.qml
@@ -0,0 +1,139 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+
+import "./BikeStyle"
+
+Rectangle {
+ property string arrowSource: "images/nav_nodir.png"
+ property string address: "-"
+ property string distance: "0"
+ property string unit: "m"
+
+ width: UILayout.naviPageGuideRadius * 2
+ height: width
+ radius: width
+ color: Colors.naviPageGuideBackground
+ z: 1
+
+ Rectangle {
+ width: UILayout.naviPageGuideRadius
+ height: width
+ radius: 10
+ color: Colors.naviPageGuideBackground
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ }
+
+ Image {
+ id: guideArrow
+ anchors {
+ top: parent.top
+ topMargin: UILayout.naviPageGuideArrowTopMargin
+ left: parent.left
+ leftMargin: UILayout.naviPageGuideArrowLeftMargin
+ }
+ source: arrowSource
+ width: UILayout.naviPageGuideArrowWidth
+ height: UILayout.naviPageGuideArrowHeight
+ }
+
+ Text {
+ id: naviAddressText
+ anchors {
+ baseline: parent.bottom
+ baselineOffset: -UILayout.naviPageGuideAddressBaselineMargin
+ right: parent.right
+ rightMargin: UILayout.naviPageGuideAddressRightMargin
+ }
+ width: 123
+ horizontalAlignment: Text.AlignRight
+ color: Colors.naviPageGuideAddressColor
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.naviPageGuideAddressTextSize
+ }
+ fontSizeMode: Text.Fit
+ wrapMode: Text.WordWrap
+ minimumPixelSize: 9
+ text: address
+ }
+
+ Text {
+ id: naviUnit
+ anchors {
+ baseline: naviAddressText.baseline
+ baselineOffset: -UILayout.naviPageGuideDistanceBaselineMargin
+ right: naviAddressText.right
+ }
+ color: Colors.naviPageGuideUnitColor
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.naviPageGuideUnitTextSize
+ }
+ text: datastore.smallUnit
+ }
+
+ Text {
+ id: naviDistance
+ anchors {
+ baseline: naviUnit.baseline
+ right: naviUnit.left
+ rightMargin: 10
+ }
+ color: Colors.naviPageGuideTextColor
+ font {
+ family: "Montserrat, Bold"
+ weight: Font.Bold
+ pixelSize: UILayout.naviPageGuideDistanceTextSize
+ }
+ text: Math.round(datastore.convertSmallDistance(distance) / 10) * 10
+ }
+
+ Image {
+ source: "images/navigation_widget_shadow.png"
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ horizontalCenterOffset: 1
+ verticalCenterOffset: 1
+ }
+ z: -1
+ }
+}
diff --git a/basicsuite/ebike-ui/NaviPage.qml b/basicsuite/ebike-ui/NaviPage.qml
new file mode 100644
index 0000000..e6c8805
--- /dev/null
+++ b/basicsuite/ebike-ui/NaviPage.qml
@@ -0,0 +1,691 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtPositioning 5.3
+import QtLocation 5.9
+import QtQuick.VirtualKeyboard 2.1
+
+import "./BikeStyle"
+
+Page {
+ id: mapContainer
+ property var startCoordinate: QtPositioning.coordinate(36.131961, -115.153048)
+ property var destinationCoordinate: QtPositioning.coordinate(90, 0)
+ property var targetPlace
+ property real totalDistance
+ property real metersPerSecond // This is calculated at the start of a trip
+ property real totalTravelTime: totalDistance / metersPerSecond
+ property real naviGuideSegmentDistance: 0
+ property string naviGuideArrowSource: "images/nav_nodir.png"
+ property string naviGuideDistance
+ property string naviGuideAddress: "-"
+ property alias routeQuery: routeQuery
+ property alias routeModel: routeModel
+ property alias targetEdit: targetEdit
+ property var routeSegmentList
+ property var currentSegment
+ property int routeSegment
+ property int pathSegment
+
+ function resetDemo() {
+ // Clear/reset everything
+ naviGuideArrowSource = "images/nav_nodir.png"
+ naviGuideDistance = "-"
+ naviGuideAddress = "-"
+ navigation.active = false
+ navigationArrowAnimation.stop()
+ targetEdit.clear()
+
+ map.focus = true
+ routeQuery.clearWaypoints()
+ routeModel.reset()
+ navigationArrowItem.coordinate = navigation.coordinate
+ destinationCoordinate = QtPositioning.coordinate(90, 0)
+ map.center = navigation.coordinate
+ }
+
+ TextField {
+ id: targetEdit
+ width: UILayout.naviPageLocationWidth
+ height: UILayout.naviPageLocationHeight
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ top: parent.top
+ topMargin: UILayout.naviPageLocationTopMargin
+ }
+ background: Rectangle {
+ radius: UILayout.naviPageLocationRadius
+ implicitWidth: UILayout.naviPageLocationWidth
+ implicitHeight: UILayout.naviPageLocationHeight
+ border.color: Colors.naviPageSuggestionBorder
+ border.width: targetEdit.activeFocus ? 2 : 0
+ }
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.naviPageSearchTextSize
+ }
+ cursorVisible: !navigation.active
+ inputMethodHints: Qt.ImhNoPredictiveText // This should disable lookup on the device
+
+ leftPadding: UILayout.naviPageLocationLeftPadding
+ rightPadding: UILayout.naviPageSearchIconMargin + UILayout.naviPageSearchIconWidth
+ placeholderText: qsTr("<i>Where do you want to go?</i>")
+ z: 1
+ autoScroll: false
+
+ // Update search text whenever text is edited
+ onTextEdited: suggest.search = text
+
+ // If in navigation mode, disable editing
+ readOnly: navigation.active
+
+ Image {
+ source: navigation.active ? "images/search_cancel.png" : "images/search.png"
+ width: UILayout.naviPageSearchIconWidth
+ height: UILayout.naviPageSearchIconHeight
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ right: parent.right
+ rightMargin: UILayout.naviPageSearchIconMargin
+ }
+ visible: !suggest.loading
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: navigation.active
+ onClicked: {
+ naviGuideArrowSource = "images/nav_nodir.png"
+ naviGuideDistance = "-"
+ naviGuideAddress = "-"
+ navigation.active = false
+ navigationArrowAnimation.stop()
+ targetEdit.clear()
+ }
+ }
+ }
+
+ // Show a busy indicator whenever suggestions are loading
+ BusyIndicator {
+ width: height
+ anchors {
+ top: targetEdit.top
+ bottom: targetEdit.bottom
+ right: targetEdit.right
+ rightMargin: UILayout.naviPageSearchIconMargin
+ }
+ running: suggest.loading
+ }
+
+ Image {
+ id: naviInputShadow
+ source: "images/small_input_box_shadow.png"
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ horizontalCenterOffset: 1
+ verticalCenterOffset: 1
+ }
+ visible: false
+ z: -2
+ }
+ }
+
+ ListView {
+ id: targetList
+ anchors {
+ top: targetEdit.bottom;
+ topMargin: UILayout.naviPageSuggestionsOffset
+ horizontalCenter: targetEdit.horizontalCenter
+ }
+ width: UILayout.naviPageLocationWidth
+ height: 3 * UILayout.naviPageSuggestionHeight
+ model: suggestions
+ visible: targetEdit.activeFocus && !navigation.active
+ z: 1
+ currentIndex: -1
+
+ delegate: Component {
+ Rectangle {
+ width: parent.width
+ height: UILayout.naviPageSuggestionHeight
+ color: "white"
+ border.color: Colors.naviPageSuggestionsDivider
+ border.width: 1
+
+ Text {
+ width: parent.width
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: 10
+ elide: Text.ElideRight
+ text: placename
+ color: Colors.naviPageSuggestionText
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.naviPageSuggestionTextSize
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: targetList.currentIndex = index
+ }
+ }
+ }
+
+ onCurrentIndexChanged: {
+ // Called when the currentIndex is reset, ignore that
+ if (targetList.currentIndex == -1)
+ return;
+
+ // Get current place name
+ targetPlace = model.get(targetList.currentIndex)
+ suggestions.addToMostRecent(targetPlace)
+
+ // Reset search
+ targetList.currentIndex = -1
+ // Update current text
+ targetEdit.text = targetPlace.place_name
+ // Clear the model and stop any search
+ suggestions.clear()
+ suggest.stopSuggest()
+
+ targetEdit.cursorPosition = 0
+ targetEdit.ensureVisible(1)
+ navigation.active = true
+ map.focus = true
+
+ destinationCoordinate = QtPositioning.coordinate(targetPlace.center[1], targetPlace.center[0]);
+ routeQuery.clearWaypoints()
+ routeQuery.addWaypoint(map.center)
+ routeQuery.addWaypoint(destinationCoordinate)
+ routeQuery.travelModes = RouteQuery.BicycleTravel
+ routeQuery.routeOptimizations = RouteQuery.ShortestRoute
+ routeModel.update()
+ }
+ }
+
+ // Zoom and location icons
+ NaviButton {
+ id: gpsCenter
+ anchors {
+ top: parent.top
+ topMargin: UILayout.naviPageIconTopMargin
+ right: parent.right
+ rightMargin: UILayout.naviPageIconRightMargin
+ }
+ iconSource: "images/map_locate.png"
+ onClicked: {
+ navigationArrowItem.coordinate = navigation.coordinate
+ map.center = navigation.coordinate
+ }
+ }
+
+ NaviButton {
+ id: zoomIn
+ anchors {
+ top: gpsCenter.bottom
+ topMargin: UILayout.naviPageIconSpacing
+ right: parent.right
+ rightMargin: UILayout.naviPageIconRightMargin
+ }
+ iconSource: "images/map_zoomin.png"
+ autoRepeat: true
+ onClicked: navigation.zoomlevel += 0.1
+ }
+
+ NaviButton {
+ id: zoomOut
+ anchors {
+ top: zoomIn.bottom
+ topMargin: UILayout.naviPageIconSpacing
+ right: parent.right
+ rightMargin: UILayout.naviPageIconRightMargin
+ }
+ iconSource: "images/map_zoomout.png"
+ autoRepeat: true
+ onClicked: navigation.zoomlevel -= 0.1
+ }
+
+ NaviGuide {
+ id: naviGuide
+ anchors {
+ right: parent.right
+ bottom: parent.bottom
+ rightMargin: UILayout.naviPageGuideRightMargin
+ bottomMargin: UILayout.naviPageGuideBottomMargin
+ }
+ arrowSource: naviGuideArrowSource
+ distance: naviGuideDistance
+ address: naviGuideAddress
+ visible: navigation.active
+ }
+
+ NaviTripInfo {
+ id: totalTripInfo
+ z: 1
+ visible: navigation.active
+ anchors {
+ bottom: parent.bottom
+ bottomMargin: UILayout.naviPageTripBottomMargin
+ horizontalCenter: parent.horizontalCenter
+ }
+
+ remainingDistance: totalDistance
+ remainingTravelTime: totalTravelTime
+ }
+
+ RouteQuery {
+ id: routeQuery
+ numberAlternativeRoutes: 0
+ }
+
+ function updateNaviGuide(segment, nextsegment) {
+ var maneuver = segment.maneuver;
+ naviGuideSegmentDistance = maneuver.distanceToNextInstruction;
+ navigationArrowAnimation.pathDistance = naviGuideSegmentDistance;
+ naviGuideDistance = maneuver.distanceToNextInstruction;
+
+ if (nextsegment) {
+ var nextmaneuver = nextsegment.maneuver;
+ naviGuideAddress = nextmaneuver.instructionText;
+ switch (nextmaneuver.direction) {
+ case RouteManeuver.NoDirection:
+ naviGuideArrowSource = "images/nav_nodir.png";
+ break;
+ case RouteManeuver.DirectionForward:
+ naviGuideArrowSource = "images/nav_straight.png";
+ break;
+ case RouteManeuver.DirectionBearRight:
+ naviGuideArrowSource = "images/nav_bear_r.png";
+ break;
+ case RouteManeuver.DirectionLightRight:
+ naviGuideArrowSource = "images/nav_light_right.png";
+ break;
+ case RouteManeuver.DirectionRight:
+ naviGuideArrowSource = "images/nav_right.png";
+ break;
+ case RouteManeuver.DirectionHardRight:
+ naviGuideArrowSource = "images/nav_hard_r.png";
+ break;
+ case RouteManeuver.DirectionUTurnRight:
+ naviGuideArrowSource = "images/nav_uturn_r.png";
+ break;
+ case RouteManeuver.DirectionUTurnLeft:
+ naviGuideArrowSource = "images/nav_uturn_l.png";
+ break;
+ case RouteManeuver.DirectionHardLeft:
+ naviGuideArrowSource = "images/nav_hard_l.png";
+ break;
+ case RouteManeuver.DirectionLeft:
+ naviGuideArrowSource = "images/nav_left.png";
+ break;
+ case RouteManeuver.DirectionLightLeft:
+ naviGuideArrowSource = "images/nav_light_left.png";
+ break;
+ case RouteManeuver.DirectionBearLeft:
+ naviGuideArrowSource = "images/nav_bear_l.png";
+ break;
+ }
+ } else {
+ naviGuideAddress = "-";
+ naviGuideArrowSource = "images/nav_nodir.png";
+ }
+ }
+
+ function setNextAnimation() {
+ if (!navigation.active)
+ return;
+ var position = QtPositioning.coordinate(currentSegment.maneuver.position.longitude,
+ currentSegment.maneuver.position.latitude);
+
+ // Update the navigation instructions
+ if (pathSegment === 0) {
+ var nextSegment = routeSegmentList[routeSegment + 1];
+ updateNaviGuide(currentSegment, nextSegment);
+ }
+
+ var startPos = navigationArrowItem.coordinate;
+ var path = currentSegment.path;
+ pathSegment += 1;
+ if (pathSegment >= path.length) {
+ routeSegment += 1;
+ currentSegment = routeSegmentList[routeSegment];
+ if (!currentSegment) {
+ naviGuideArrowSource = "images/nav_nodir.png";
+ navigation.active = false;
+ targetEdit.clear();
+ return;
+ }
+ pathSegment = 0;
+ setNextAnimation();
+ return;
+ }
+ var endPos = path[pathSegment];
+
+ // Calculate new direction
+ var oldDir = navigationArrowAnimation.rotationDirection;
+ var newDir = startPos.azimuthTo(endPos);
+
+ // Calculate the duration of the animation
+ var diff = oldDir - newDir;
+ if (Math.abs(diff) < 15)
+ navigationArrowAnimation.rotationDuration = 0;
+ else if (diff < -180)
+ navigationArrowAnimation.rotationDuration = (diff + 360) * 5;
+ else if (diff > 180)
+ navigationArrowAnimation.rotationDuration = (360 - diff) * 5;
+ else
+ navigationArrowAnimation.rotationDuration = Math.abs(diff) * 5;
+
+ // Set animation details
+ var pathDistance = startPos.distanceTo(endPos);
+ var nextDistance = navigationArrowAnimation.pathDistance - pathDistance;
+ var nextRemainingDistance = navigationArrowAnimation.remainingDistance - pathDistance;
+ navigationArrowAnimation.coordinateDuration = pathDistance * 40;
+ navigationArrowAnimation.rotationDirection = startPos.azimuthTo(endPos);
+ navigationArrowAnimation.pathDistance = nextDistance;
+ navigationArrowAnimation.remainingDistance = nextRemainingDistance;
+ navigationArrowAnimation.sourceCoordinate = startPos;
+ navigationArrowAnimation.targetCoordinate = endPos;
+ navigationArrowAnimation.start();
+ }
+
+ RouteModel {
+ id: routeModel
+ plugin: map.plugin
+ query: routeQuery
+ onStatusChanged: {
+ if (status === RouteModel.Ready) {
+ switch (count) {
+ case 0:
+ // technically not an error
+ console.log('mapping error', errorString)
+ break
+ case 1:
+ break
+ }
+ } else if (status === RouteModel.Error) {
+ console.log('mapping error', errorString)
+ }
+ }
+ onRoutesChanged: {
+ if (count === 0)
+ return;
+ var route = routeModel.get(0);
+
+ totalDistance = route.distance;
+ metersPerSecond = route.distance / route.travelTime;
+ navigationArrowAnimation.remainingDistance = route.distance;
+
+ routeManeuverModel.clear();
+ currentSegment = route.segments[0];
+ routeSegmentList = route.segments;
+
+ routeSegment = 0;
+ pathSegment = 0;
+ setNextAnimation();
+ }
+ }
+
+ // Model that is used to display individual instructions
+ ListModel {
+ id: routeManeuverModel
+ }
+
+ Map {
+ id: map
+ gesture.enabled: true
+ anchors.fill: parent
+ plugin: mapboxgl
+
+ center: navigation.coordinate
+ zoomLevel: navigation.zoomlevel
+ bearing: navigation.direction
+
+ onCenterChanged: {
+ suggest.center = map.center
+ }
+
+ Behavior on bearing {
+ RotationAnimation {
+ duration: 250
+ direction: RotationAnimation.Shortest
+ }
+ }
+
+ Behavior on center {
+ id: centerBehavior
+ enabled: true
+ CoordinateAnimation { duration: 1500 }
+ }
+
+ MapQuickItem {
+ id: navigationArrowItem
+ z: 3
+ coordinate: navigation.routePosition
+ anchorPoint.x: navigationArrowImage.width / 2
+ anchorPoint.y: navigationArrowImage.height / 2
+ sourceItem: Image {
+ id: navigationArrowImage
+ source: "images/map_location_arrow.png"
+ width: 30
+ height: 30
+ }
+ }
+
+ MapQuickItem {
+ id: navigationCircleItem
+ z: 2
+ coordinate: navigation.routePosition
+ anchorPoint.x: navigationCircleImage.width / 2
+ anchorPoint.y: navigationCircleImage.height / 2
+ sourceItem: Image {
+ id: navigationCircleImage
+ source: "images/blue_circle_gps_area.png"
+ width: 100
+ height: 100
+ }
+ }
+
+ MapQuickItem {
+ id: destinationQuickItem
+ z: 3
+ coordinate: destinationCoordinate
+ anchorPoint.x: destinationImage.width / 2
+ anchorPoint.y: destinationImage.height - 10
+ sourceItem: Image {
+ id: destinationImage
+ source: "images/map_destination.png"
+ width: 40
+ height: 40
+ }
+ visible: navigation.active
+ }
+
+ MapItemView {
+ model: routeModel
+ delegate: routeDelegate
+ }
+
+ Component {
+ id: routeDelegate
+
+ MapRoute {
+ id: route
+ route: routeData
+ line.color: "#3698e8"
+ line.width: 6
+ smooth: true
+ visible: index === 0 // Show only one route (numberAlternativeRoutes not respected yet)
+ }
+ }
+
+ SequentialAnimation {
+ id: navigationArrowAnimation
+ property real rotationDuration: 0;
+ property real rotationDirection: 0;
+ property real coordinateDuration: 0;
+ property real pathDistance: 0;
+ property real remainingDistance: 0;
+ property var sourceCoordinate: navigation.routePosition;
+ property var targetCoordinate: startCoordinate;
+
+ RotationAnimation {
+ target: map
+ property: "bearing"
+ duration: navigationArrowAnimation.rotationDuration
+ to: navigationArrowAnimation.rotationDirection
+ direction: RotationAnimation.Shortest
+ }
+
+ ParallelAnimation {
+ CoordinateAnimation {
+ target: map
+ property: "center"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.targetCoordinate
+ }
+
+ CoordinateAnimation {
+ target: navigationArrowItem
+ property: "coordinate"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.targetCoordinate
+ }
+
+ CoordinateAnimation {
+ target: navigationCircleItem
+ property: "coordinate"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.targetCoordinate
+ }
+
+ NumberAnimation {
+ target: mapContainer
+ property: "naviGuideDistance"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.pathDistance
+ }
+
+ NumberAnimation {
+ target: mapContainer
+ property: "totalDistance"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.remainingDistance
+ }
+ }
+
+ onStopped: setNextAnimation()
+ }
+
+ MouseArea {
+ // Whenever the user taps on the map, move focus to it
+ anchors.fill: parent
+ onClicked: map.focus = true
+ }
+ }
+
+ Plugin {
+ id: mapboxgl
+ name: "mapbox"
+ PluginParameter {
+ name: "mapbox.access_token"
+ value: "pk.eyJ1IjoibWFwYm94NHF0IiwiYSI6ImNpd3J3eDE0eDEzdm8ydHM3YzhzajlrN2oifQ.keEkjqm79SiFDFjnesTcgQ"
+ }
+ PluginParameter {
+ name: "mapbox.mapping.map_id"
+ value: "mapbox.run-bike-hike"
+ }
+ }
+
+ states: [
+ State {
+ name: "";
+ when: !navigation.active
+ PropertyChanges {
+ target: targetEdit
+ width: UILayout.naviPageLocationWidth
+ anchors.topMargin: UILayout.naviPageLocationTopMargin
+ anchors.bottomMargin: 0
+ }
+ AnchorChanges {
+ target: targetEdit
+ anchors.top: parent.top
+ anchors.bottom: undefined
+ }
+ PropertyChanges {
+ target: naviInputShadow
+ visible: false
+ }
+ },
+ State {
+ name: "NAVIGATING";
+ when: navigation.active
+ PropertyChanges {
+ target: targetEdit
+ width: UILayout.naviPageTripWidth
+ anchors.topMargin: 0
+ anchors.bottomMargin: UILayout.naviPageTripSearchMargin
+ }
+ AnchorChanges {
+ target: targetEdit
+ anchors.top: undefined
+ anchors.bottom: totalTripInfo.top
+ }
+ PropertyChanges {
+ target: naviInputShadow
+ visible: true
+ }
+ }
+ ]
+
+ transitions: Transition {
+ NumberAnimation {
+ properties: "width"
+ easing.type: Easing.InOutQuad
+ duration: 250
+ }
+ AnchorAnimation {
+ duration: 250
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/NaviTripInfo.qml b/basicsuite/ebike-ui/NaviTripInfo.qml
new file mode 100644
index 0000000..e024aab
--- /dev/null
+++ b/basicsuite/ebike-ui/NaviTripInfo.qml
@@ -0,0 +1,147 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+
+import "./BikeStyle"
+
+Rectangle {
+ property real remainingDistance: 0
+ property real remainingTravelTime: 0
+ property var remainingDistanceSplit: datastore.splitDistance(remainingDistance, true)
+ property var remainingTravelTimeSplit: datastore.splitDuration(remainingTravelTime)
+
+ width: UILayout.naviPageTripWidth
+ height: UILayout.naviPageTripHeight
+ radius: UILayout.naviPageTripRadius
+ color: Colors.naviPageTripBackground
+
+ Rectangle {
+ width: UILayout.naviPageTripDividerWidth
+ height: UILayout.naviPageTripDividerHeight
+ radius: 2
+ color: Colors.naviPageTripDivider
+ anchors.centerIn: parent
+ }
+
+ Item {
+ height: parent.height
+ width: naviTripDistanceRemainingText.width + 5 + naviTripDistanceUnitText.width
+ anchors {
+ verticalCenter: parent.verticalCenter
+ horizontalCenter: parent.horizontalCenter
+ horizontalCenterOffset: -parent.width / 4
+ }
+
+ Text {
+ id: naviTripDistanceRemainingText
+ anchors.verticalCenter: parent.verticalCenter
+ color: Colors.naviPageGuideTextColor
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.naviPageTripTotalTextSize
+ }
+ text: remainingDistanceSplit.decimal ? remainingDistanceSplit.value.toFixed(1) : remainingDistanceSplit.value.toFixed(0)
+ }
+
+ Text {
+ id: naviTripDistanceUnitText
+ anchors {
+ verticalCenter: parent.verticalCenter
+ left: naviTripDistanceRemainingText.right
+ leftMargin: 5
+ }
+ color: Colors.naviPageGuideUnitColor
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.naviPageTripTotalUnitSize
+ }
+ text: remainingDistanceSplit.unit
+ }
+ }
+
+
+ Item {
+ height: parent.height
+ width: naviTripTimeRemainingText.width + 5 + naviTripTimeUnitText.width
+ anchors {
+ verticalCenter: parent.verticalCenter
+ horizontalCenter: parent.horizontalCenter
+ horizontalCenterOffset: parent.width / 4
+ }
+
+ Text {
+ id: naviTripTimeRemainingText
+ anchors.verticalCenter: parent.verticalCenter
+ color: Colors.naviPageGuideTextColor
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.naviPageTripTotalTextSize
+ }
+ text: remainingTravelTimeSplit.value.toFixed(0)
+ }
+
+ Text {
+ id: naviTripTimeUnitText
+ anchors {
+ verticalCenter: parent.verticalCenter
+ left: naviTripTimeRemainingText.right
+ leftMargin: 5
+ }
+ color: Colors.naviPageGuideUnitColor
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.naviPageTripTotalUnitSize
+ }
+ text: remainingTravelTimeSplit.unit
+ }
+ }
+
+ Image {
+ source: "images/small_input_box_shadow.png"
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ horizontalCenterOffset: 1
+ verticalCenterOffset: 1
+ }
+ z: -1
+ }
+}
diff --git a/basicsuite/ebike-ui/SpeedView.qml b/basicsuite/ebike-ui/SpeedView.qml
new file mode 100644
index 0000000..d62e710
--- /dev/null
+++ b/basicsuite/ebike-ui/SpeedView.qml
@@ -0,0 +1,526 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import DataStore 1.0
+import "./BikeStyle"
+
+Rectangle {
+ width: UILayout.speedViewRadius * 2 + 2
+ height: UILayout.speedViewRadius * 2 + 2
+ color: "transparent"
+ radius: width * 0.5
+ z: 1
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ top: parent.top
+ topMargin: UILayout.speedViewTop
+ }
+ property int dotcount: UILayout.speedViewDots
+ property int curvewidth: UILayout.speedViewInnerWidth
+ property int speedTextSize: UILayout.speedTextSize
+ property int speedBaselineOffset: UILayout.speedBaselineOffset + 1
+ property int innerRadius: UILayout.speedViewInnerRadius
+ property int speedUnitsSize: UILayout.speedUnitsSize
+ property int speedUnitBaselineOffset: UILayout.speedTextUnitMargin
+ property int speedIconsOffset: UILayout.speedIconsCenterOffset
+ property int speedInfoTextsOffset: 0
+ property int speedInfoTextsSize: UILayout.speedInfoTextsSize
+ property int speedInfoUnitsOffset: UILayout.speedInfoUnitsOffset
+ property int assistPowerIconOffset: UILayout.assistPowerIconOffset
+ property bool enlarged: false
+ property alias cornerRectangle: cornerRectangle
+ property bool showZero: false
+
+ signal showMain()
+
+ // Speed info
+ Text {
+ id: speedText
+ anchors {
+ horizontalCenter: speedView.horizontalCenter
+ baseline: speedView.bottom
+ baselineOffset: -speedBaselineOffset
+ }
+
+ color: Colors.speedText
+ font {
+ family: "Teko, Light"
+ weight: Font.Light
+ pixelSize: speedTextSize
+ }
+ text: showZero ? "0" : datastore.speed.toFixed(0)
+ }
+
+ Text {
+ id: speedUnit
+ anchors {
+ horizontalCenter: speedText.horizontalCenter
+ baseline: speedText.baseline
+ baselineOffset: speedUnitBaselineOffset
+ }
+
+ color: Colors.speedUnit
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: speedUnitsSize
+ }
+ text: datastore.unit === DataStore.Kmh ? qsTr("km/h") : qsTr("mph")
+ }
+
+ // Average speed info
+ Text {
+ id: averageSpeedText
+ anchors {
+ baseline: speedText.baseline
+ baselineOffset: speedInfoTextsOffset
+ horizontalCenter: speedText.horizontalCenter
+ horizontalCenterOffset: -speedIconsOffset
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.averageSpeedText
+ font {
+ family: "Teko, Light"
+ weight: Font.Light
+ pixelSize: speedInfoTextsSize
+ }
+ text: datastore.averagespeed.toFixed(0)
+ }
+
+ Text {
+ id: averageSpeedUnit
+ anchors {
+ horizontalCenter: averageSpeedText.horizontalCenter
+ baseline: averageSpeedText.baseline
+ baselineOffset: speedInfoUnitsOffset
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.averageSpeedUnit
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: speedUnitsSize
+ }
+ horizontalAlignment: Text.AlignHCenter
+ text: datastore.unit === DataStore.Kmh ? qsTr("AVG\nkm/h") : qsTr("AVG\nmph")
+ }
+
+ Image {
+ id: averageSpeedIcon
+ width: UILayout.averageSpeedIconWidth
+ height: UILayout.averageSpeedIconHeight
+ source: "images/speed.png"
+ anchors {
+ horizontalCenter: averageSpeedText.horizontalCenter
+ bottom: averageSpeedText.top
+ bottomMargin: UILayout.averageSpeedIconMargin
+ }
+ visible: speedView.state !== "CORNERED"
+ }
+
+ // Assist info
+ Text {
+ id: assistDistanceText
+ anchors {
+ baseline: speedText.baseline
+ baselineOffset: speedInfoTextsOffset
+ horizontalCenter: speedText.horizontalCenter
+ horizontalCenterOffset: speedIconsOffset
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.assistDistanceText
+ font {
+ family: "Teko, Light"
+ weight: Font.Light
+ pixelSize: speedInfoTextsSize
+ }
+ text: datastore.assistdistance.toFixed(0)
+ }
+
+ Text {
+ id: assistDistanceUnit
+ anchors {
+ horizontalCenter: assistDistanceText.horizontalCenter
+ baseline: assistDistanceText.baseline
+ baselineOffset: speedInfoUnitsOffset
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.assistDistanceUnit
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: speedUnitsSize
+ }
+ horizontalAlignment: Text.AlignHCenter
+ text: datastore.unit === DataStore.Kmh ? qsTr("km\nassist\nleft") : qsTr("mi.\nassist\nleft")
+ }
+
+ Image {
+ id: assistDistanceIcon
+ width: UILayout.assistDistanceIconWidth
+ height: UILayout.assistDistanceIconHeight
+ source: "images/battery.png"
+ anchors {
+ horizontalCenter: assistDistanceText.horizontalCenter
+ bottom: assistDistanceText.top
+ bottomMargin: UILayout.assistDistanceIconMargin
+ }
+ visible: speedView.state !== "CORNERED"
+ }
+
+ // Assist power icon
+ Image {
+ id: assistPowerIcon
+ width: UILayout.assistPowerTconWidth
+ height: UILayout.assistPowerTconHeight
+ source: "images/assist.png"
+ anchors {
+ horizontalCenter: speedView.horizontalCenter
+ top: parent.verticalCenter
+ topMargin: assistPowerIconOffset
+ }
+ visible: speedView.state !== "CORNERED"
+ }
+
+ Gradient {
+ id: assistPowerGradient
+ GradientStop { position: 0.0; color: Colors.assistPowerGradientStart }
+ GradientStop { position: 1.0; color: Colors.assistPowerGradientEnd }
+ }
+
+ // Assist power circles
+ Rectangle {
+ id: assistPowerIcon1
+ width: UILayout.assistPowerCircleRadius * 2
+ height: width
+ radius: UILayout.assistPowerCircleRadius
+ anchors {
+ right: assistPowerIcon2.left
+ rightMargin: UILayout.assistPowerCircleOffset
+ bottom: assistPowerIcon2.bottom
+ bottomMargin: UILayout.assistPowerCircleVerticalOffset
+ }
+ color: Colors.assistPowerEmpty
+ gradient: datastore.assistpower > 2.0 ? assistPowerGradient : null
+ visible: speedView.state !== "CORNERED"
+ }
+
+ Rectangle {
+ id: assistPowerIcon2
+ width: UILayout.assistPowerCircleRadius * 2
+ height: width
+ radius: UILayout.assistPowerCircleRadius
+ anchors {
+ right: parent.horizontalCenter
+ rightMargin: UILayout.assistPowerCircleOffset / 2
+ top: assistPowerIcon.bottom
+ topMargin: UILayout.assistPowerCircleTopMargin
+ }
+ color: Colors.assistPowerEmpty
+ gradient: datastore.assistpower > 25.0 ? assistPowerGradient : null
+ visible: speedView.state !== "CORNERED"
+ }
+
+ Rectangle {
+ id: assistPowerIcon3
+ width: UILayout.assistPowerCircleRadius * 2
+ height: width
+ radius: UILayout.assistPowerCircleRadius
+ anchors {
+ left: parent.horizontalCenter
+ leftMargin: UILayout.assistPowerCircleOffset / 2
+ top: assistPowerIcon.bottom
+ topMargin: UILayout.assistPowerCircleTopMargin
+ }
+ color: Colors.assistPowerEmpty
+ gradient: datastore.assistpower > 50.0 ? assistPowerGradient : null
+ visible: speedView.state !== "CORNERED"
+ }
+
+ Rectangle {
+ id: assistPowerIcon4
+ width: UILayout.assistPowerCircleRadius * 2
+ height: width
+ radius: UILayout.assistPowerCircleRadius
+ anchors {
+ left: assistPowerIcon3.right
+ leftMargin: UILayout.assistPowerCircleOffset
+ bottom: assistPowerIcon3.bottom
+ bottomMargin: UILayout.assistPowerCircleVerticalOffset
+ }
+ color: Colors.assistPowerEmpty
+ gradient: datastore.assistpower > 75.0 ? assistPowerGradient : null
+ visible: speedView.state !== "CORNERED"
+ }
+
+ // Numbers for speed and battery level
+ Text {
+ anchors {
+ baseline: parent.bottom
+ baselineOffset: 20
+ right: parent.horizontalCenter
+ rightMargin: 10
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.dottedRing
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.ringValueText
+ }
+ text: "0"
+ }
+
+ Text {
+ anchors {
+ baseline: parent.bottom
+ baselineOffset: 20
+ left: parent.horizontalCenter
+ leftMargin: 10
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.dottedRing
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.ringValueText
+ }
+ text: "0 %"
+ }
+
+ Text {
+ anchors {
+ baseline: parent.verticalCenter
+ baselineOffset: -10
+ right: parent.left
+ rightMargin: 9
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.dottedRing
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.ringValueText
+ }
+ text: "30"
+ }
+
+ Text {
+ anchors {
+ baseline: parent.verticalCenter
+ baselineOffset: -10
+ left: parent.right
+ leftMargin: 9
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.dottedRing
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.ringValueText
+ }
+ text: "50 %"
+ }
+
+ Text {
+ anchors {
+ baseline: parent.top
+ baselineOffset: -10
+ right: parent.horizontalCenter
+ rightMargin: 10
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.dottedRing
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.ringValueText
+ }
+ text: "60"
+ }
+
+ Text {
+ anchors {
+ baseline: parent.top
+ baselineOffset: -10
+ left: parent.horizontalCenter
+ leftMargin: 10
+ }
+ visible: speedView.state !== "CORNERED"
+
+ color: Colors.dottedRing
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.ringValueText
+ }
+ text: "100 %"
+ }
+
+ Rectangle {
+ id: cornerRectangle
+ width: UILayout.speedViewRadiusMinified
+ height: width
+ // By default this is centered, and invisible
+ anchors.horizontalCenter: speedView.horizontalCenter
+ anchors.verticalCenter: speedView.verticalCenter
+ color: "transparent"
+ radius: 10
+ z: -1
+
+ Image {
+ source: "images/small_speedometer_arrow.png"
+ width: UILayout.speedometerCornerArrowWidth
+ height: UILayout.speedometerCornerArrowHeight
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ visible: swipeView.currentIndex != 1
+ }
+ }
+
+ Image {
+ source: "images/small_speedometer_shadow.png"
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ horizontalCenterOffset: 1
+ verticalCenterOffset: 1
+ }
+ z: -1
+
+ visible: speedView.state == "CORNERED"
+ }
+
+ Canvas {
+ id: speedArc
+ anchors.fill: parent
+
+ Component.onCompleted: {
+ datastore.onSpeedChanged.connect(speedArc.requestPaint)
+ datastore.onBatterylevelChanged.connect(speedArc.requestPaint)
+ }
+
+ onPaint: {
+ var ctx = getContext("2d");
+ ctx.reset();
+
+ var currentRadius = (speedView.width - 2) / 2;
+ var centerX = speedView.width / 2;
+ var centerY = speedView.height / 2;
+
+ // Draw the dotted circle (if not in corner mode)
+ if (speedView.state !== "CORNERED") {
+ ctx.fillStyle = Colors.dottedRing;
+ var angleStep = Math.PI * 2.0 / dotcount;
+ for (var angle = 0; angle <= Math.PI * 2; angle += angleStep) {
+ var x = currentRadius * Math.cos(angle);
+ var y = currentRadius * Math.sin(angle);
+ ctx.fillRect(centerX + x, centerY + y, 2, 2);
+ }
+ }
+
+ // Draw speed and battery view bases
+ ctx.lineCap = "round";
+ ctx.strokeStyle = Colors.dottedRing;
+ ctx.lineWidth = curvewidth;
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, innerRadius,
+ UILayout.speedViewSpeedStart, UILayout.speedViewSpeedEnd);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, innerRadius,
+ UILayout.speedViewBatteryStart, UILayout.speedViewBatteryEnd, true);
+ ctx.stroke();
+
+ // Draw speed gradient
+ if (!showZero) {
+ var speedArcLength = UILayout.speedViewSpeedEnd - UILayout.speedViewSpeedStart;
+ var speedAngle = Math.min(UILayout.speedViewSpeedEnd,
+ UILayout.speedViewSpeedStart + speedArcLength * (datastore.speed / 60));
+ var speedAngleX = Math.cos(speedAngle) * innerRadius;
+ var speedAngleY = Math.sin(speedAngle) * innerRadius;
+ var speedGradient = ctx.createLinearGradient(centerX, centerY + innerRadius,
+ centerX + speedAngleX, centerY + speedAngleY);
+ speedGradient.addColorStop(0.0, Colors.speedGradientStart);
+ speedGradient.addColorStop(1.0, Colors.speedGradientEnd);
+ ctx.strokeStyle = speedGradient;
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, innerRadius,
+ UILayout.speedViewSpeedStart, speedAngle);
+ ctx.stroke();
+ }
+
+ // Draw battery gradient
+ var batteryArcLength = UILayout.speedViewBatteryEnd - UILayout.speedViewBatteryStart;
+ var batteryAngle = Math.max(UILayout.speedViewBatteryEnd,
+ UILayout.speedViewBatteryStart + batteryArcLength * (datastore.batterylevel / 100));
+ var batteryAngleX = Math.cos(batteryAngle) * innerRadius;
+ var batteryAngleY = Math.sin(batteryAngle) * innerRadius;
+ var batteryGradient = ctx.createLinearGradient(centerX, centerY + innerRadius,
+ centerX + batteryAngleX, centerY + batteryAngleY);
+ //centerX, centerY - innerRadius);
+ batteryGradient.addColorStop(0.0, Colors.batteryGradientStart);
+ batteryGradient.addColorStop(1.0, Colors.batteryGradientEnd);
+ ctx.strokeStyle = batteryGradient;
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, innerRadius,
+ UILayout.speedViewBatteryStart, batteryAngle, true);
+ ctx.stroke();
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (enlarged)
+ enlarged = false
+ else if (swipeView.currentIndex === 1)
+ enlarged = true
+ else
+ speedView.showMain()
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/StatsBox.qml b/basicsuite/ebike-ui/StatsBox.qml
new file mode 100644
index 0000000..7d6f24c
--- /dev/null
+++ b/basicsuite/ebike-ui/StatsBox.qml
@@ -0,0 +1,157 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+import DataStore 1.0
+
+import "./BikeStyle"
+
+// Top-left corner, stats
+Item {
+ width: 320
+ height: UILayout.topViewHeight
+
+ Image {
+ id: tripIcon
+ width: UILayout.statsIconWidth
+ height: UILayout.statsIconHeight
+ source: "images/trip.png"
+ anchors {
+ top: parent.top
+ left: parent.left
+ topMargin: UILayout.statsIconTop
+ leftMargin: UILayout.statsIconLeft
+ }
+ }
+
+ Text {
+ id: tripText
+ color: Colors.distanceText
+ anchors {
+ top: tripIcon.top
+ topMargin: UILayout.statsTextTopOffset
+ left: tripIcon.right
+ leftMargin: UILayout.statsTextSeparator
+ }
+ font {
+ family: "Montserrat, Bold"
+ weight: Font.Bold
+ pixelSize: UILayout.statsTextSize
+ }
+ text: datastore.trip.toFixed(1)
+ }
+
+ Text {
+ id: tripUnitText
+ color: Colors.distanceUnit
+ anchors {
+ baseline: tripIcon.bottom
+ baselineOffset: -UILayout.statsUnitBaselineOffset
+ left: tripIcon.right
+ leftMargin: UILayout.statsTextSeparator
+ }
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.statsTextSize
+ }
+ text: datastore.unit === DataStore.Kmh ? "km" : "mi."
+ }
+
+ Image {
+ id: calIcon
+ width: UILayout.statsIconWidth
+ height: UILayout.statsIconHeight
+ source: "images/calories.png"
+ anchors {
+ top: tripIcon.bottom
+ left: parent.left
+ topMargin: UILayout.statsIconSeparator
+ leftMargin: UILayout.statsIconLeft
+ }
+ }
+
+ Text {
+ id: calText
+ color: Colors.distanceText
+ anchors {
+ top: calIcon.top
+ topMargin: UILayout.statsTextTopOffset
+ left: calIcon.right
+ leftMargin: UILayout.statsTextSeparator
+ }
+ font {
+ family: "Montserrat, Bold"
+ weight: Font.Bold
+ pixelSize: UILayout.statsTextSize
+ }
+ text: datastore.calories.toFixed(0)
+ }
+
+ Text {
+ id: calUnitText
+ color: Colors.distanceUnit
+ anchors {
+ baseline: calIcon.bottom
+ baselineOffset: -UILayout.statsUnitBaselineOffset
+ left: calIcon.right
+ leftMargin: UILayout.statsTextSeparator
+ }
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.statsTextSize
+ }
+ text: "kcal"
+ }
+
+ Rectangle {
+ width: UILayout.horizontalViewSeparatorWidth
+ height: UILayout.horizontalViewSeparatorHeight
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ color: Colors.separator
+ }
+
+ Rectangle {
+ width: UILayout.verticalViewSeparatorWidth
+ height: UILayout.verticalViewSeparatorHheightTop
+ anchors.top: parent.top
+ anchors.right: parent.right
+ color: Colors.separator
+ }
+}
diff --git a/basicsuite/ebike-ui/StatsPage.qml b/basicsuite/ebike-ui/StatsPage.qml
new file mode 100644
index 0000000..959b813
--- /dev/null
+++ b/basicsuite/ebike-ui/StatsPage.qml
@@ -0,0 +1,314 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtCharts 2.2
+import DataStore 1.0
+
+import "./BikeStyle"
+import "moment.js" as Moment
+
+Page {
+ id: statsPage
+ background: Rectangle {
+ color: Colors.mainBackground
+ }
+
+ // Function for pretty-printing duration
+ function splitDuration(duration) {
+ var hours = Math.floor(duration / 3600);
+ var minutes = Math.floor((duration % 3600) / 60);
+ var seconds = Math.floor(duration % 60);
+ if (minutes < 10)
+ minutes = "0" + minutes;
+ return hours + ":" + minutes;
+ }
+
+ function timestampToReadable(timestamp) {
+ return Moment.moment.unix(timestamp).calendar();
+ }
+
+ // On new trip data (save clicked), switch index to new trip
+ Connections {
+ target: tripdata
+ onTripDataSaved: tripView.setCurrentIndex(index)
+ }
+
+ RoundButton {
+ id: endTrip
+ width: UILayout.statsEndtripWidth
+ height: UILayout.statsEndtripHeight
+ radius: height / 2
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ top: parent.top
+ topMargin: UILayout.statsEndtripMargin
+ }
+
+ background: Rectangle {
+ anchors.fill: parent
+ color: parent.down ? Colors.statsButtonPressed : "transparent"
+ radius: parent.radius
+ border.color: Colors.statsButtonActive
+ border.width: parent.down ? 0 : 1
+ }
+
+ contentItem: Text {
+ color: Colors.statsButtonActiveText
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("END TRIP")
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.statsEndtripTextSize
+ }
+ }
+ visible: tripView.currentIndex === 0
+ onClicked: tripdata.endTrip()
+ }
+
+ Text {
+ id: tripDateText
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.top
+ verticalCenterOffset: UILayout.statsEndtripMargin + UILayout.statsEndtripHeight / 2
+ }
+ color: Colors.statsButtonActiveText
+ text: qsTr("YESTERDAY")
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.statsEndtripTextSize
+ }
+ visible: tripView.currentIndex > 0
+ }
+
+ RoundButton {
+ id: previousChart
+ width: UILayout.statsTripButtonWidth
+ height: UILayout.statsTripButtonHeight
+ radius: height / 2
+ anchors {
+ left: parent.left
+ top: endTrip.top
+ leftMargin: UILayout.statsTripButtonMarginSide
+ }
+ enabled: tripView.currentIndex > 0
+
+ background: Rectangle {
+ anchors.fill: parent
+ color: parent.down ? Colors.statsButtonPressed : "transparent"
+ radius: parent.radius
+ border.color: enabled ? Colors.statsButtonActive : Colors.statsButtonInactive
+ border.width: parent.down ? 0 : 1
+ }
+
+ contentItem: Item {}
+ Image {
+ anchors.centerIn: parent
+ source: "images/arrow_left.png"
+ opacity: parent.enabled ? 1.0 : 0.3
+ }
+
+ onClicked: tripView.decrementCurrentIndex()
+ }
+
+ RoundButton {
+ id: nextChart
+ width: UILayout.statsTripButtonWidth
+ height: UILayout.statsTripButtonHeight
+ radius: height / 2
+ anchors {
+ right: parent.right
+ top: endTrip.top
+ rightMargin: UILayout.statsTripButtonMarginSide
+ }
+ enabled: tripView.currentIndex < tripView.count - 1
+
+ background: Rectangle {
+ anchors.fill: parent
+ color: parent.down ? Colors.statsButtonPressed : "transparent"
+ radius: parent.radius
+ border.color: enabled ? Colors.statsButtonActive : Colors.statsButtonInactive
+ border.width: parent.down ? 0 : 1
+ }
+
+ contentItem: Item {}
+ Image {
+ anchors.centerIn: parent
+ source: "images/arrow_right.png"
+ opacity: parent.enabled ? 1.0 : 0.3
+ }
+
+ onClicked: tripView.incrementCurrentIndex()
+ }
+
+ // Odometer
+ Item {
+ width: odometerText.width + odometerUnit.width + odometerDescription.width + 2 * 4
+ anchors {
+ right: parent.right
+ bottom: parent.top
+ rightMargin: UILayout.statsOdometerMarginRight
+ bottomMargin: -UILayout.statsOdometerBaselineOffset
+ }
+
+ Text {
+ id: odometerDescription
+ anchors.right: odometerText.left
+ anchors.rightMargin: 4
+ anchors.baseline: parent.bottom
+ color: Colors.statsDescriptionText
+ text: qsTr("Total")
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.statsDescriptionTextSize
+ }
+ }
+
+ Text {
+ id: odometerText
+ anchors.right: odometerUnit.left
+ anchors.rightMargin: 4
+ anchors.baseline: parent.bottom
+ color: Colors.statsValueText
+ text: datastore.odometer.toFixed(1)
+ font {
+ family: "Montserrat, Bold"
+ weight: Font.Bold
+ pixelSize: UILayout.statsValueTextSize
+ }
+ }
+
+ Text {
+ id: odometerUnit
+ anchors.right: parent.right
+ anchors.baseline: parent.bottom
+ color: Colors.statsDescriptionText
+ text: datastore.unit === DataStore.Kmh ? qsTr("km") : qsTr("mi.")
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.statsDescriptionTextSize
+ }
+ }
+ }
+
+ SwipeView {
+ id: tripView
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: endTrip.bottom
+ bottom: tripChart.top
+ leftMargin: UILayout.statsTripButtonMarginSide
+ rightMargin: UILayout.statsTripButtonMarginSide
+ topMargin: UILayout.statsTopMargin
+ }
+ // Hide any excess content, since we are using margins
+ clip: true
+
+ // Load data on first show
+ Component.onCompleted: tripdata.refresh()
+
+ onCurrentIndexChanged: tripDateText.text = timestampToReadable(tripdata.get(currentIndex).starttime)
+
+ Repeater {
+ model: tripdata
+
+ Column {
+ width: tripView.width
+ height: tripView.height
+
+ ColumnSpacer {
+ color: Colors.statsSeparator
+ }
+
+ StatsRow {
+ leftTitle: qsTr("Duration (h:m)")
+ leftValue: splitDuration(duration)
+ rightTitle: datastore.unit === DataStore.Kmh ? qsTr("Max. speed (km/h)") : qsTr("Max. speed (mph)")
+ rightValue: maxspeed.toFixed(1)
+ }
+
+ ColumnSpacer {
+ color: Colors.statsSeparator
+ }
+
+ StatsRow {
+ leftTitle: datastore.unit === DataStore.Kmh ? qsTr("Distance (km)") : qsTr("Distance (mi.)")
+ leftValue: distance.toFixed(1)
+ rightTitle: datastore.unit === DataStore.Kmh ? qsTr("Avg. speed (km/h)") : qsTr("Avg. speed (mph)")
+ rightValue: avgspeed.toFixed(1)
+ }
+
+ ColumnSpacer {
+ color: Colors.statsSeparator
+ }
+
+ StatsRow {
+ leftTitle: qsTr("Calories (kcal)")
+ leftValue: calories.toFixed(1)
+ rightTitle: qsTr("Ascent (m)")
+ rightValue: ascent.toFixed(1)
+ }
+
+ ColumnSpacer {
+ color: Colors.statsSeparator
+ }
+ }
+ }
+ }
+
+ TripChart {
+ id: tripChart
+ width: UILayout.chartWidth
+ height: UILayout.chartHeight
+ anchors {
+ bottom: parent.bottom
+ right: parent.right
+ bottomMargin: UILayout.chartBottomMargin
+ rightMargin: UILayout.chartRightMargin
+ }
+ animationRunning: tripView.currentIndex === 0
+ tripDetails: tripdata.get(currentIndex)
+ currentIndex: tripView.currentIndex
+ }
+}
diff --git a/basicsuite/ebike-ui/StatsRow.qml b/basicsuite/ebike-ui/StatsRow.qml
new file mode 100644
index 0000000..df0f523
--- /dev/null
+++ b/basicsuite/ebike-ui/StatsRow.qml
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+
+import "./BikeStyle"
+
+Item {
+ property string leftTitle
+ property string leftValue
+ property string rightTitle
+ property string rightValue
+ height: UILayout.statsHeight
+ width: parent.width
+
+ Text {
+ text: leftTitle
+ anchors.left: parent.left
+ height: parent.height
+ color: Colors.statsDescriptionText
+ verticalAlignment: Text.AlignVCenter
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.statsDescriptionTextSize
+ }
+ }
+
+ Text {
+ text: leftValue
+ anchors.right: parent.horizontalCenter
+ anchors.rightMargin: UILayout.statsCenterOffset
+ height: parent.height
+ color: Colors.statsValueText
+ verticalAlignment: Text.AlignVCenter
+ font {
+ family: "Montserrat, Bold"
+ weight: Font.Bold
+ pixelSize: UILayout.statsValueTextSize
+ }
+ }
+
+ Text {
+ text: rightTitle
+ anchors.left: parent.horizontalCenter
+ anchors.leftMargin: UILayout.statsCenterOffset
+ height: parent.height
+ color: Colors.statsDescriptionText
+ verticalAlignment: Text.AlignVCenter
+ font {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.statsDescriptionTextSize
+ }
+ }
+
+ Text {
+ text: rightValue
+ anchors.right: parent.right
+ height: parent.height
+ color: Colors.statsValueText
+ verticalAlignment: Text.AlignVCenter
+ font {
+ family: "Montserrat, Bold"
+ weight: Font.Bold
+ pixelSize: UILayout.statsValueTextSize
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/ToggleSwitch.qml b/basicsuite/ebike-ui/ToggleSwitch.qml
new file mode 100644
index 0000000..94af651
--- /dev/null
+++ b/basicsuite/ebike-ui/ToggleSwitch.qml
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+
+import "./BikeStyle"
+
+Switch {
+ id: control
+
+ implicitWidth: indicator.implicitWidth
+ implicitHeight: indicator.implicitHeight
+
+ indicator: Rectangle {
+ implicitWidth: UILayout.switchWidth
+ implicitHeight: UILayout.switchHeight
+ radius: height / 2
+ y: parent.height / 2 - height / 2
+ color: control.checked ? Colors.switchBackgroundOn : Colors.switchBackgroundOff
+
+ Rectangle {
+ x: control.checked ? parent.width - width : 0
+ y: UILayout.switchHeight / 2 - UILayout.switchIndicatorRadius
+ width: 2 * UILayout.switchIndicatorRadius
+ height: 2 * UILayout.switchIndicatorRadius
+ radius: UILayout.switchIndicatorRadius
+ color: control.checked ? Colors.switchOn : Colors.switchOff
+ }
+ }
+
+ contentItem: Text {}
+}
diff --git a/basicsuite/ebike-ui/TripChart.qml b/basicsuite/ebike-ui/TripChart.qml
new file mode 100644
index 0000000..72812a5
--- /dev/null
+++ b/basicsuite/ebike-ui/TripChart.qml
@@ -0,0 +1,236 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtCharts 2.2
+import DataStore 1.0
+
+import "./BikeStyle"
+
+ChartView {
+ property var exampleTrips: [
+ [[0, 0], [15, 250], [30, 0], [30, 0], [25, 450], [20, 400], [10, 200], [10, 200], [20, 350], [20, 300], [10, 200], [10, 100], [10, 100], [10, 175], [10, 150], [15, 200], [15, 250], [10, 200], [15, 250], [0, 0]],
+ [[0, 0], [5, 100], [10, 200], [10, 200], [10, 150], [15, 250], [15, 275], [15, 200], [20, 350], [5, 50], [15, 200], [15, 250], [15, 225], [20, 300], [25, 425], [25, 400], [15, 200], [8, 125], [10, 175], [0, 0]],
+ [[0, 0], [20, 375], [15, 250], [15, 300], [15, 275], [0, 0], [10, 200], [10, 100], [10, 100], [15, 250], [10, 200], [10, 100], [10, 100], [8, 100], [25, 50], [15, 200], [15, 250], [10, 200], [8, 100], [0, 0]],
+ [[0, 0], [15, 300], [15, 200], [15, 250], [10, 450], [20, 375], [20, 350], [20, 250], [15, 200], [15, 225], [8, 150], [8, 100], [8, 125], [8, 100], [10, 150], [15, 200], [15, 250], [20, 300], [15, 250], [0, 0]]
+ ]
+ property bool animationRunning: false
+ property int currentIndex
+ property var tripDetails
+
+ id: tripChart
+ antialiasing: true
+ backgroundColor: "transparent"
+ backgroundRoundness: 0
+ title: ""
+ legend {
+ markerShape: Legend.MarkerShapeCircle
+ reverseMarkers: true
+ labelColor: "#ffffff"
+ font {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.chartLegendTextSize
+ }
+ }
+
+ margins {
+ top: 0
+ bottom: 0
+ left: 0
+ right: 0
+ }
+
+ // X-axis is a timestamp value
+ DateTimeAxis {
+ id: axisX
+ format: Qt.locale("en_US").timeFormat(Locale.ShortFormat)
+ tickCount: 3
+ // No grid and no line
+ gridVisible: false
+ lineVisible: false
+ titleVisible: false
+ labelsColor: Colors.chartTimeLabel
+ labelsFont {
+ family: "Montserrat, Light"
+ weight: Font.Light
+ pixelSize: UILayout.chartTimeLabelSize
+ }
+ }
+
+ ValueAxis {
+ id: speedAxis
+ min: 0
+ max: 50
+ gridLineColor: Colors.chartGridLine
+ labelsColor: Colors.chartSpeed
+ labelsFont {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.chartSpeedLabelSize
+ }
+ titleVisible: false
+ lineVisible: false
+ labelFormat: "%.0f"
+ }
+
+ ValueAxis {
+ id: assistAxis
+ min: 0
+ max: 500
+ labelsColor: Colors.chartAssistpower
+ labelsFont {
+ family: "Montserrat, Regular"
+ weight: Font.Normal
+ pixelSize: UILayout.chartAssistpowerLabelSize
+ }
+ titleVisible: false
+ lineVisible: false
+ gridVisible: false
+ labelFormat: "%.0f"
+ }
+
+ SplineSeries {
+ id: assistSeries
+ name: qsTr("Pedal assist (W)")
+
+ axisX: axisX
+ axisYRight: assistAxis
+ pointsVisible: true
+ }
+
+ SplineSeries {
+ id: speedSeries
+ name: datastore.unit === DataStore.Kmh ? qsTr("Speed (km/h)") : qsTr("Speed (mph)")
+
+ axisX: axisX
+ axisY: speedAxis
+ pointsVisible: true
+ }
+
+ function updateTripGraph() {
+ if (currentIndex === 0 ) {
+ // Clear all current values (resets the graph)
+ speedSeries.removePoints(0, speedSeries.count);
+ assistSeries.removePoints(0, assistSeries.count);
+ } else if (currentIndex > 0) {
+ // Clear all current values
+ speedSeries.removePoints(0, speedSeries.count);
+ assistSeries.removePoints(0, assistSeries.count);
+ var seriesdata = exampleTrips[currentIndex % 4];
+
+ var now = tripDetails.starttime * 1000;
+ var duration = tripDetails.duration / seriesdata.length;
+
+ axisX.min = new Date(now - 60000);
+ for (var i = 0; i < seriesdata.length; i++) {
+ speedSeries.append(now, seriesdata[i][0]);
+ assistSeries.append(now, seriesdata[i][1]);
+ now += duration * 1000;
+ }
+ now -= duration * 1000;
+ axisX.max = new Date(now + 60000);
+ }
+ }
+
+ onTripDetailsChanged: updateTripGraph()
+
+ onCurrentIndexChanged: updateTripGraph()
+
+ // Make sure we have a proper value here
+ onAnimationRunningChanged: tripAnimationTimer.lastUpdate = new Date().getTime()
+
+ Timer {
+ id: tripAnimationTimer
+ property real lastUpdate: new Date().getTime()
+ property int currentIndex: 0
+ property var values: [[0, 0],
+ [10, 200],
+ [15, 250],
+ [15, 250],
+ [8, 150],
+ [5, 100],
+ [20, 400],
+ [20, 375],
+ [15, 275],
+ [15, 250],
+ [25, 450],
+ [15, 200],
+ [15, 200],
+ [10, 175],
+ [10, 150],
+ [5, 100],
+ [8, 125],
+ [10, 200],
+ [15, 250],
+ [0, 0]]
+
+ // Animate only if visible on screen
+ running: animationRunning && (swipeView.currentIndex === 0)
+ repeat: true
+ interval: 200
+ onTriggered: {
+ var now = new Date().getTime();
+ // Load a few initial numbers if empty
+ if (speedSeries.count === 0) {
+ speedSeries.append(now, values[0][0]);
+ assistSeries.append(now, values[0][1]);
+ speedSeries.append(now + 5000, values[1][0]);
+ assistSeries.append(now + 5000, values[1][1]);
+ speedSeries.append(now + 10000, values[2][0]);
+ assistSeries.append(now + 10000, values[2][1]);
+ speedSeries.append(now + 15000, values[3][0]);
+ assistSeries.append(now + 15000, values[3][1]);
+ currentIndex = 4;
+ }
+
+ if (now - lastUpdate > 5000) {
+ speedSeries.append(now + 15000, values[currentIndex][0]);
+ assistSeries.append(now + 15000, values[currentIndex][1]);
+ if (speedSeries.count > 17)
+ speedSeries.remove(0);
+ if (assistSeries.count > 17)
+ assistSeries.remove(0);
+ currentIndex += 1;
+ if (currentIndex == 20)
+ currentIndex = 0;
+ lastUpdate = now;
+ }
+ axisX.min = new Date(now - 50 * 1000);
+ axisX.max = new Date(now);
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/ViewTab.qml b/basicsuite/ebike-ui/ViewTab.qml
new file mode 100644
index 0000000..040436a
--- /dev/null
+++ b/basicsuite/ebike-ui/ViewTab.qml
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+import "./BikeStyle"
+
+Item {
+ property alias musicPlayerSwitch: musicPlayerSwitch
+
+ signal resetDemo
+
+ Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Text {
+ height: UILayout.configurationItemHeight
+ width: parent.width
+ text: qsTr("VIEW")
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.configurationTitleSize
+ }
+ color: Colors.tabTitleColor
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ ColumnSpacer {
+ color: Colors.tabItemBorder
+ }
+
+ ConfigurationItem {
+ description: qsTr("Show FPS")
+
+ ToggleSwitch {
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ right: parent.right
+ }
+ checked: fps.visible
+ onCheckedChanged: fps.visible = checked
+ }
+ }
+
+ ColumnSpacer {
+ color: Colors.tabItemBorder
+ }
+
+ ConfigurationItem {
+ description: qsTr("Audio controls")
+
+ ToggleSwitch {
+ id: musicPlayerSwitch
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ right: parent.right
+ }
+ checked: false
+ onCheckedChanged: musicPlayer.state = (checked ? "" : "hidden")
+ }
+ }
+
+ ColumnSpacer {
+ color: Colors.tabItemBorder
+ }
+
+ ConfigurationItem {
+ description: qsTr("Reset demo")
+
+ RoundButton {
+ id: resetButton
+ width: UILayout.unitButtonWidthMargin * 2 + mphText.implicitWidth
+ height: UILayout.unitButtonHeight
+ radius: height / 2
+ anchors {
+ verticalCenter: parent.verticalCenter
+ right: parent.right
+ }
+
+ background: Rectangle {
+ anchors.fill: parent
+ color: parent.down ? Colors.statsButtonPressed : "transparent"
+ radius: parent.radius
+ border.color: Colors.statsButtonActive
+ border.width: parent.down ? 0 : 1
+ }
+
+ contentItem: Text {
+ id: mphText
+ text: "RESET DEMO"
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.unitFontSize
+ }
+ color: Colors.statsButtonActiveText
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ onClicked: resetDemo()
+ }
+ }
+
+ ColumnSpacer {
+ color: Colors.tabItemBorder
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/app.pro b/basicsuite/ebike-ui/app.pro
new file mode 100644
index 0000000..dd993f1
--- /dev/null
+++ b/basicsuite/ebike-ui/app.pro
@@ -0,0 +1,141 @@
+QT += quick
+CONFIG += c++11
+TARGET = ebike
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which as been marked deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+include(../shared/shared.pri)
+b2qtdemo_deploy_defaults()
+
+content.files = \
+ qtquickcontrols2.conf \
+ main.qml \
+ NaviPage.qml \
+ StatsPage.qml \
+ MainPage.qml \
+ SpeedView.qml \
+ StatsBox.qml \
+ NaviBox.qml \
+ LightsBox.qml \
+ ModeBox.qml \
+ ClockView.qml \
+ MusicPlayer.qml \
+ ConfigurationDrawer.qml \
+ IconifiedTabButton.qml \
+ GeneralTab.qml \
+ ColumnSpacer.qml \
+ BikeInfoTab.qml \
+ TripChart.qml \
+ FpsItem.qml \
+ NaviGuide.qml \
+ NaviTripInfo.qml \
+ ViewTab.qml \
+ moment.js \
+ StatsRow.qml \
+ ConfigurationItem.qml \
+ NaviButton.qml \
+ ToggleSwitch.qml \
+ mostrecent.bson
+
+content.path = $$DESTPATH
+
+style.files = \
+ BikeStyle/Colors.qml \
+ BikeStyle/qmldir \
+ BikeStyle/UILayout.qml
+
+
+images.files = \
+ images/lights_off.png \
+ images/lights_on.png \
+ images/map-marker.png \
+ images/trip.png \
+ images/calories.png \
+ images/nextsong.png \
+ images/nextsong_pressed.png \
+ images/play.png \
+ images/play_pressed.png \
+ images/prevsong.png \
+ images/prevsong_pressed.png \
+ images/speed.png \
+ images/battery.png \
+ images/assist.png \
+ images/arrow_left.png \
+ images/top_curtain_drag.png \
+ images/spinner.png \
+ images/checkmark.png \
+ images/nav_left.png \
+ images/nav_right.png \
+ images/nav_straight.png \
+ images/small_speedometer_arrow.png \
+ images/map_locate.png \
+ images/map_zoomin.png \
+ images/map_zoomout.png \
+ images/info.png \
+ images/info_selected.png \
+ images/list.png \
+ images/list_selected.png \
+ images/settings.png \
+ images/settings_selected.png \
+ images/curtain_up_arrow.png \
+ images/search.png \
+ images/search_cancel.png \
+ images/fps_icon.png \
+ images/arrow_right.png \
+ images/curtain_shadow_handle.png \
+ images/map_btn_shadow.png \
+ images/map_destination.png \
+ images/map_location_arrow.png \
+ images/small_speedometer_shadow.png \
+ images/navigation_widget_shadow.png \
+ images/small_input_box_shadow.png \
+ images/nav_bear_l.png \
+ images/nav_bear_r.png \
+ images/nav_hard_l.png \
+ images/nav_hard_r.png \
+ images/nav_light_left.png \
+ images/nav_light_right.png \
+ images/nav_nodir.png \
+ images/nav_uturn_l.png \
+ images/nav_uturn_r.png \
+ images/pause.png \
+ images/pause_pressed.png \
+ images/ok.png \
+ images/warning.png \
+ images/bike-battery.png \
+ images/bike-brakes.png \
+ images/bike-chain.png \
+ images/bike-frontwheel.png \
+ images/bike-gears.png \
+ images/bike-rearwheel.png \
+ images/bike-light.png \
+ images/blue_circle_gps_area.png
+
+OTHER_FILES += $${images.files}
+INSTALLS += images
+images.path = $$DESTPATH/images
+export(images.files)
+export(images.path)
+
+OTHER_FILES += $${style.files}
+INSTALLS += style
+style.path = $$DESTPATH/BikeStyle
+export(style.files)
+export(style.path)
+export(OTHER_FILES)
+export(INSTALLS)
+
+
+OTHER_FILES += $${content.files}
+
+INSTALLS += target content
+
diff --git a/basicsuite/ebike-ui/brightnesscontroller.cpp b/basicsuite/ebike-ui/brightnesscontroller.cpp
new file mode 100644
index 0000000..a6e2e30
--- /dev/null
+++ b/basicsuite/ebike-ui/brightnesscontroller.cpp
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QFile>
+
+#include "brightnesscontroller.h"
+
+static const char brightnessSource[] = "/sys/class/backlight/backlight/brightness";
+static const int brightnessMax = 6;
+static const int brightnessMin = 1;
+
+BrightnessController::BrightnessController(QObject *parent)
+ : QObject(parent)
+ , m_automatic(false)
+{
+ QFile brightnessfile(brightnessSource);
+ if (brightnessfile.exists() && brightnessfile.open(QIODevice::ReadOnly)) {
+ char data[1];
+ if (brightnessfile.read(data, 1)) {
+ m_brightness = static_cast<int>(data[0] - '0');
+ }
+ } else {
+ // By default set to half
+ m_brightness = (brightnessMax + brightnessMin) / 2;
+ }
+}
+
+void BrightnessController::setBrightness(int brightness)
+{
+ if (m_brightness == brightness)
+ return;
+
+ // Valid values are between 1-6
+ if (brightness < brightnessMin || brightness > brightnessMax)
+ return;
+
+ QFile brightnessfile(brightnessSource);
+ if (brightnessfile.exists() && brightnessfile.open(QIODevice::WriteOnly)) {
+ char data[1] = {static_cast<char>('0' + brightness)};
+ if (brightnessfile.write(data, 1) == 1) {
+ m_brightness = brightness;
+ emit brightnessChanged(m_brightness);
+ }
+ } else {
+ // File does not exists, simulate changes
+ m_brightness = brightness;
+ emit brightnessChanged(m_brightness);
+ }
+}
+
+void BrightnessController::setAutomatic(bool automatic)
+{
+ if (m_automatic == automatic)
+ return;
+
+ m_automatic = automatic;
+ emit automaticChanged(m_automatic);
+}
diff --git a/basicsuite/ebike-ui/brightnesscontroller.h b/basicsuite/ebike-ui/brightnesscontroller.h
new file mode 100644
index 0000000..7b85f58
--- /dev/null
+++ b/basicsuite/ebike-ui/brightnesscontroller.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 BRIGHTNESSCONTROLLER_H
+#define BRIGHTNESSCONTROLLER_H
+
+#include <QObject>
+
+class BrightnessController : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int brightness READ brightness WRITE setBrightness NOTIFY brightnessChanged)
+ Q_PROPERTY(bool automatic READ automatic WRITE setAutomatic NOTIFY automaticChanged)
+
+public:
+ explicit BrightnessController(QObject *parent = nullptr);
+
+public:
+ int brightness() const { return m_brightness; }
+ bool automatic() const { return m_automatic; }
+
+ void setBrightness(int brightness);
+ void setAutomatic(bool automatic);
+
+signals:
+ void brightnessChanged(int brightness);
+ void automaticChanged(bool automatic);
+
+public slots:
+
+private:
+ int m_brightness;
+ bool m_automatic;
+};
+
+#endif // BRIGHTNESSCONTROLLER_H
diff --git a/basicsuite/ebike-ui/datamodelplugin/datamodelplugin.pro b/basicsuite/ebike-ui/datamodelplugin/datamodelplugin.pro
new file mode 100644
index 0000000..ce22eec
--- /dev/null
+++ b/basicsuite/ebike-ui/datamodelplugin/datamodelplugin.pro
@@ -0,0 +1,39 @@
+TEMPLATE = lib
+CONFIG += plugin
+QT += qml quick positioning charts
+
+TARGET = ebikedatamodelplugin
+
+SOURCES += plugin.cpp \
+ $$PWD/../socketclient.cpp \
+ $$PWD/../datastore.cpp \
+ $$PWD/../navigation.cpp \
+ $$PWD/../mapboxsuggestions.cpp \
+ $$PWD/../suggestionsmodel.cpp \
+ $$PWD/../mapbox.cpp \
+ $$PWD/../brightnesscontroller.cpp \
+ $$PWD/../fpscounter.cpp \
+ $$PWD/../tripdatamodel.cpp
+
+HEADERS += \
+ $$PWD/../socketclient.h \
+ $$PWD/../datastore.h \
+ $$PWD/../navigation.h \
+ $$PWD/../mapboxsuggestions.h \
+ $$PWD/../suggestionsmodel.h \
+ $$PWD/../mapbox.h \
+ $$PWD/../brightnesscontroller.h \
+ $$PWD/../fpscounter.h \
+ $$PWD/../tripdatamodel.h
+
+INCLUDEPATH += $$PWD/../
+
+pluginfiles.files += \
+ qmldir \
+
+B2QT_DEPLOYPATH = /data/user/qt/qmlplugins/DataStore
+target.path += $$B2QT_DEPLOYPATH
+pluginfiles.path += $$B2QT_DEPLOYPATH
+
+INSTALLS += target pluginfiles
+
diff --git a/basicsuite/ebike-ui/datamodelplugin/plugin.cpp b/basicsuite/ebike-ui/datamodelplugin/plugin.cpp
new file mode 100644
index 0000000..098a6a8
--- /dev/null
+++ b/basicsuite/ebike-ui/datamodelplugin/plugin.cpp
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 <QtQml/QQmlExtensionPlugin>
+#include <QtQml/qqml.h>
+#include <QtQml/QQmlEngine>
+#include <QtQml/QQmlContext>
+#include <qdebug.h>
+
+#include "datastore.h"
+#include "tripdatamodel.h"
+#include "navigation.h"
+#include "mapbox.h"
+#include "mapboxsuggestions.h"
+#include "suggestionsmodel.h"
+#include "brightnesscontroller.h"
+#include "fpscounter.h"
+
+class QExampleQmlPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+
+public:
+
+ void registerTypes(const char *uri)
+ {
+ Q_UNUSED(uri);
+ qmlRegisterType<DataStore>("DataStore", 1, 0, "DataStore");
+ }
+
+ void initializeEngine(QQmlEngine *engine, const char *uri)
+ {
+ Q_UNUSED(uri);
+
+ // Setup data store for connection to backend server
+ DataStore *datastore = new DataStore(engine);
+ datastore->connectToServer("datasocket");
+
+ // Setup mapbox and suggestions
+ MapBox *mapbox = new MapBox(engine);
+ MapBoxSuggestions *suggest = new MapBoxSuggestions(mapbox, engine);
+
+ // Setup navigation container
+ Navigation *navi = new Navigation(mapbox, engine);
+
+ // Brightness controller
+ BrightnessController *brightness = new BrightnessController(engine);
+
+ // FPS counter
+ FpsCounter *fps = new FpsCounter(engine);
+
+ QQmlContext *context = engine->rootContext();
+ context->setContextProperty("datastore", datastore);
+ context->setContextProperty("tripdata", datastore->tripDataModel());
+ context->setContextProperty("navigation", navi);
+ context->setContextProperty("suggest", suggest);
+ context->setContextProperty("suggestions", suggest->suggestions());
+ context->setContextProperty("brightness", brightness);
+ context->setContextProperty("fps", fps);
+ }
+};
+
+
+#include "plugin.moc"
diff --git a/basicsuite/ebike-ui/datamodelplugin/qmldir b/basicsuite/ebike-ui/datamodelplugin/qmldir
new file mode 100644
index 0000000..3018d21
--- /dev/null
+++ b/basicsuite/ebike-ui/datamodelplugin/qmldir
@@ -0,0 +1,2 @@
+module DataStore
+plugin ebikedatamodelplugin
diff --git a/basicsuite/ebike-ui/datastore.cpp b/basicsuite/ebike-ui/datastore.cpp
new file mode 100644
index 0000000..73567ac
--- /dev/null
+++ b/basicsuite/ebike-ui/datastore.cpp
@@ -0,0 +1,300 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QJsonArray>
+#include <QMetaObject>
+#include <QMetaMethod>
+
+#include "socketclient.h"
+#include "datastore.h"
+#include "tripdatamodel.h"
+
+// Speed conversions from m/s to km/h and mph
+static const double msToKmh = 3.6;
+static const double msToMph = 2.2369418519393043;
+// Distance conversions to and from m to km and mi
+static const double mToKm = 0.001;
+static const double mToMi = 0.000621371;
+static const double kmToM = 1000.0;
+static const double miToM = 1609.344;
+static const double mToYd = 1760.0 / 1609.344;
+
+DataStore::DataStore(QObject *parent)
+ : QObject(parent)
+ , m_client(new SocketClient(this))
+ , m_unit(Mph)
+ , m_trips(new TripDataModel(this, this))
+{
+ connect(m_client, &SocketClient::connected, this, &DataStore::requestStatus);
+ connect(m_client, &SocketClient::newMessage, this, &DataStore::parseMessage);
+}
+
+void DataStore::connectToServer(const QString &servername)
+{
+ m_client->connectToServer(servername);
+}
+
+void DataStore::getTrips()
+{
+ m_client->sendToServer(QJsonObject{{"method", "gettrips"}});
+}
+
+void DataStore::endTrip()
+{
+ m_client->sendToServer(QJsonObject{{"method", "endtrip"}});
+}
+
+void DataStore::toggleMode()
+{
+ setMode(mode() == Cruise ? Sport : Cruise);
+}
+
+void DataStore::resetDemo()
+{
+ m_client->sendToServer(QJsonObject{{"method", "reset"}});
+}
+
+double DataStore::speed() const
+{
+ return convertSpeed(m_properties.value("speed").toDouble());
+}
+
+double DataStore::topSpeed() const
+{
+ return convertSpeed(m_properties.value("topspeed").toDouble());
+}
+
+double DataStore::averageSpeed() const
+{
+ return convertSpeed(m_current.value("distance").toDouble() / m_current.value("duration").toDouble());
+}
+
+double DataStore::odometer() const
+{
+ return convertDistance(m_properties.value("odometer").toDouble());
+}
+
+double DataStore::trip() const
+{
+ return convertDistance(m_current.value("distance").toDouble());
+}
+
+double DataStore::calories() const
+{
+ return m_current.value("calories").toDouble();
+}
+
+double DataStore::assistDistance() const
+{
+ return convertDistance(m_properties.value("assistdistance").toDouble());
+}
+
+double DataStore::assistPower() const
+{
+ return m_properties.value("assistpower").toDouble();
+}
+
+double DataStore::batteryLevel() const
+{
+ return m_properties.value("batterylevel").toDouble();
+}
+
+bool DataStore::lights() const
+{
+ return m_properties.value("lights").toBool();
+}
+
+DataStore::Mode DataStore::mode() const
+{
+ return static_cast<Mode>(m_properties.value("mode").toInt());
+}
+
+DataStore::Unit DataStore::unit() const
+{
+ return m_unit;
+}
+
+int DataStore::arrow() const
+{
+ return m_properties.value("arrow").toInt();
+}
+
+double DataStore::legDistance() const
+{
+ return m_properties.value("legdistance").toDouble();
+}
+
+double DataStore::tripRemaining() const
+{
+ return m_properties.value("tripremaining").toDouble();
+}
+
+QString DataStore::smallUnit() const
+{
+ return m_unit == Kmh ? "m" : "yd";
+}
+
+void DataStore::setLights(bool lights)
+{
+ if (m_properties.value("lights").toBool() == lights)
+ return;
+
+ m_properties.insert("lights", lights);
+ m_client->sendToServer(QJsonObject{{"method", "set"}, {"key", "lights"}, {"value", lights}});
+ emit lightsChanged();
+}
+
+void DataStore::setMode(Mode mode)
+{
+ if (DataStore::mode() == mode)
+ return;
+
+ m_properties.insert("mode", mode);
+ m_client->sendToServer(QJsonObject{{"method", "set"}, {"key", "mode"}, {"value", static_cast<int>(mode)}});
+ emit modeChanged();
+}
+
+void DataStore::setUnit(DataStore::Unit unit)
+{
+ if (m_unit == unit)
+ return;
+
+ m_unit = unit;
+ emit unitChanged();
+ // Also emit all the other signals that are affected by mode change
+ emit speedChanged();
+ emit averagespeedChanged();
+ emit odometerChanged();
+ emit tripChanged();
+ emit assistdistanceChanged();
+ emit smallUnitChanged(smallUnit());
+}
+
+double DataStore::convertSpeed(double speed) const
+{
+ return speed * (m_unit == Kmh ? msToKmh : msToMph);
+}
+
+double DataStore::convertDistance(double distance) const
+{
+ return distance * (m_unit == Kmh ? mToKm : mToMi);
+}
+
+double DataStore::convertSmallDistance(double distance) const
+{
+ return distance * (m_unit == Kmh ? 1.0 : mToYd);
+}
+
+QString DataStore::getSmallUnit() const
+{
+ return m_unit == Kmh ? "m" : "yd";
+}
+
+QJsonObject DataStore::splitDistance(double distance, bool round) const
+{
+ if (m_unit == Kmh) {
+ if (distance >= kmToM)
+ return QJsonObject{{"value", distance * mToKm}, {"unit", "km"}, {"decimal", true}};
+ else {
+ if (round)
+ distance = qRound(distance / 10) * 10;
+ return QJsonObject{{"value", distance}, {"unit", "m"}, {"decimal", false}};
+ }
+ } else {
+ if (distance >= miToM)
+ return QJsonObject{{"value", distance * mToMi}, {"unit", "mi."}, {"decimal", true}};
+ else {
+ distance *= mToYd;
+ if (round)
+ distance = qRound(distance / 10) * 10;
+ return QJsonObject{{"value", distance}, {"unit", "yd"}, {"decimal", false}};
+ }
+ }
+}
+
+QJsonObject DataStore::splitDuration(double duration) const
+{
+ if (duration >= 60.0)
+ return QJsonObject{{"value", duration / 60.0}, {"unit", "min"}};
+ else
+ return QJsonObject{{"value", duration}, {"unit", "s"}};
+}
+
+void DataStore::requestStatus()
+{
+ m_client->sendToServer(QJsonObject{{"method", "getall"}});
+}
+
+void DataStore::emitByName(const QString &valuename)
+{
+ // Use QMetaObject information to find a proper signal
+ const QMetaObject *meta = metaObject();
+
+ // Find the notifier signal
+ QString signalName = QString("%1Changed()").arg(valuename);
+ int methodIndex = meta->indexOfSignal(signalName.toLatin1().constData());
+ meta->method(methodIndex).invoke(this, Qt::AutoConnection);
+}
+
+void DataStore::parseMessage(const QJsonObject &message)
+{
+ QString method = message.value("method").toString();
+
+ // If we are updating just one, simply insert new value and emit
+ if (method == "updateone") {
+ QString key = message.value("key").toString();
+ m_properties.insert(key, message.value("value"));
+ emitByName(key);
+ // If we are updating many, then iterate over the list and update each value
+ } else if (method == "updatemany") {
+ foreach (const QJsonValue &value, message.value("values").toArray()) {
+ QJsonObject obj = value.toObject();
+ QString key = obj.value("key").toString();
+ m_properties.insert(key, obj.value("value"));
+ emitByName(key);
+ }
+ } else if (method == "trips") {
+ m_trips->setTrips(message.value("trips").toArray());
+ } else if (method == "trip") {
+ m_trips->addTrip(message.value("trip").toObject());
+ } else if (method == "currenttrip") {
+ m_current = message.value("currenttrip").toObject();
+ m_trips->setCurrentTrip(m_current);
+ emit currentTripChanged();
+ } else if (method == "reset") {
+ emit demoReset();
+ }
+}
diff --git a/basicsuite/ebike-ui/datastore.h b/basicsuite/ebike-ui/datastore.h
new file mode 100644
index 0000000..20fa388
--- /dev/null
+++ b/basicsuite/ebike-ui/datastore.h
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 DATASTORE_H
+#define DATASTORE_H
+
+#include <QObject>
+#include <QJsonObject>
+
+class SocketClient;
+class TripDataModel;
+
+class DataStore: public QObject
+{
+ Q_OBJECT
+
+ // Different measured properties
+ Q_PROPERTY(double speed READ speed NOTIFY speedChanged)
+ Q_PROPERTY(double topspeed READ topSpeed NOTIFY topspeedChanged)
+ Q_PROPERTY(double averagespeed READ averageSpeed NOTIFY currentTripChanged)
+ Q_PROPERTY(double odometer READ odometer NOTIFY odometerChanged)
+ Q_PROPERTY(double trip READ trip NOTIFY currentTripChanged)
+ Q_PROPERTY(double calories READ calories NOTIFY currentTripChanged)
+ Q_PROPERTY(double assistdistance READ assistDistance NOTIFY assistdistanceChanged)
+ Q_PROPERTY(double assistpower READ assistPower NOTIFY assistpowerChanged)
+ Q_PROPERTY(double batterylevel READ batteryLevel NOTIFY batterylevelChanged)
+
+ // Toggles for lights and mode
+ Q_PROPERTY(bool lights READ lights WRITE setLights NOTIFY lightsChanged)
+ Q_PROPERTY(Mode mode READ mode WRITE setMode NOTIFY modeChanged)
+
+ // Current units
+ Q_PROPERTY(Unit unit READ unit WRITE setUnit NOTIFY unitChanged)
+ Q_PROPERTY(QString smallUnit READ smallUnit NOTIFY smallUnitChanged)
+
+ // Navigation
+ Q_PROPERTY(int arrow READ arrow NOTIFY arrowChanged)
+ Q_PROPERTY(double legdistance READ legDistance NOTIFY legdistanceChanged)
+ Q_PROPERTY(double tripremaining READ tripRemaining NOTIFY tripremainingChanged)
+
+public:
+ explicit DataStore(QObject *parent = nullptr);
+
+ enum Mode { Cruise, Sport };
+ Q_ENUM(Mode)
+
+ enum Unit { Kmh, Mph };
+ Q_ENUM(Unit)
+
+public:
+ // Getters
+ double speed() const;
+ double topSpeed() const;
+ double averageSpeed() const;
+ double odometer() const;
+ double trip() const;
+ double calories() const;
+ double assistDistance() const;
+ double assistPower() const;
+ double batteryLevel() const;
+ bool lights() const;
+ Mode mode() const;
+ Unit unit() const;
+ int arrow() const;
+ double legDistance() const;
+ double tripRemaining() const;
+ QString smallUnit() const;
+
+ // Setters
+ void setLights(bool lights);
+ void setMode(Mode mode);
+ void setUnit(Unit unit);
+
+ // Get trip data model
+ TripDataModel *tripDataModel() const { return m_trips; }
+
+ // Convert speed and distance to proper units
+ Q_INVOKABLE double convertSpeed(double speed) const;
+ Q_INVOKABLE double convertDistance(double distance) const;
+ Q_INVOKABLE double convertSmallDistance(double distance) const;
+ Q_INVOKABLE QString getSmallUnit() const;
+
+ // Split and convert distance and duration to value and unit
+ Q_INVOKABLE QJsonObject splitDistance(double distance, bool round=false) const;
+ Q_INVOKABLE QJsonObject splitDuration(double duration) const;
+
+private:
+ void emitByName(const QString &valuename);
+
+signals:
+ void speedChanged();
+ void topspeedChanged();
+ void averagespeedChanged();
+ void odometerChanged();
+ void tripChanged();
+ void caloriesChanged();
+ void assistdistanceChanged();
+ void assistpowerChanged();
+ void batterylevelChanged();
+ void lightsChanged();
+ void modeChanged();
+ void unitChanged();
+ void arrowChanged();
+ void legdistanceChanged();
+ void tripremainingChanged();
+ void currentTripChanged();
+ void smallUnitChanged(QString smallUnit);
+ void demoReset();
+
+public slots:
+ void connectToServer(const QString &servername);
+ void getTrips();
+ void endTrip();
+ void toggleMode();
+ void resetDemo();
+
+private slots:
+ void requestStatus();
+ void parseMessage(const QJsonObject &message);
+
+private:
+ SocketClient *m_client;
+ QJsonObject m_properties;
+ QJsonObject m_current;
+ Unit m_unit;
+ TripDataModel *m_trips;
+ int m_smallUnit;
+};
+
+#endif // DATASTORE_H
diff --git a/basicsuite/ebike-ui/ebike-ui.pro b/basicsuite/ebike-ui/ebike-ui.pro
new file mode 100644
index 0000000..70443af
--- /dev/null
+++ b/basicsuite/ebike-ui/ebike-ui.pro
@@ -0,0 +1,7 @@
+TEMPLATE = subdirs
+CONFIG += ordered
+
+SUBDIRS += \
+ datamodelplugin \
+ app.pro
+
diff --git a/basicsuite/ebike-ui/ebike_en.ts b/basicsuite/ebike-ui/ebike_en.ts
new file mode 100644
index 0000000..14679be
--- /dev/null
+++ b/basicsuite/ebike-ui/ebike_en.ts
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="en_US">
+<context>
+ <name>BikeInfoTab</name>
+ <message>
+ <location filename="BikeInfoTab.qml" line="52"/>
+ <source>BIKE INFO</source>
+ <translation>BIKE INFO</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="67"/>
+ <source>Next maintenance</source>
+ <translation>Next maintenance</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="75"/>
+ <source>15000 km</source>
+ <translation>15000 km</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="75"/>
+ <source>10000 mi.</source>
+ <translation>10000 mi.</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="91"/>
+ <source>Battery health</source>
+ <translation>Battery health</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="99"/>
+ <source>Excellent</source>
+ <translation>Excellent</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="115"/>
+ <source>System Log</source>
+ <translation>System Log</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurationDrawer</name>
+ <message>
+ <source>LIST</source>
+ <translation type="vanished">LIST</translation>
+ </message>
+ <message>
+ <source>TBD</source>
+ <translation type="vanished">TBD</translation>
+ </message>
+</context>
+<context>
+ <name>GeneralTab</name>
+ <message>
+ <location filename="GeneralTab.qml" line="52"/>
+ <source>GENERAL</source>
+ <translation>GENERAL</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="67"/>
+ <source>Language</source>
+ <translation>Language</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="75"/>
+ <source>English</source>
+ <translation>English</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="91"/>
+ <source>Brightness</source>
+ <translation>Brightness</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="99"/>
+ <source>Auto</source>
+ <translation>Auto</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="201"/>
+ <source>Units</source>
+ <translation>Units</translation>
+ </message>
+</context>
+<context>
+ <name>ModeBox</name>
+ <message>
+ <location filename="ModeBox.qml" line="62"/>
+ <source>SPORT</source>
+ <translation>SPORT</translation>
+ </message>
+ <message>
+ <location filename="ModeBox.qml" line="78"/>
+ <source>CRUISE</source>
+ <translation>CRUISE</translation>
+ </message>
+</context>
+<context>
+ <name>MusicPlayer</name>
+ <message>
+ <location filename="MusicPlayer.qml" line="133"/>
+ <source>Singer McSongface - Some really long title here</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>NaviPage</name>
+ <message>
+ <location filename="NaviPage.qml" line="89"/>
+ <source>&lt;i&gt;Where do you want to go?&lt;/i&gt;</source>
+ <translation>&lt;i&gt;Where do you want to go?&lt;/i&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>SpeedView</name>
+ <message>
+ <location filename="SpeedView.qml" line="102"/>
+ <source>km/h</source>
+ <translation>km/h</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="102"/>
+ <source>mph</source>
+ <translation>mph</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="141"/>
+ <source>AVG
+km/h</source>
+ <translation>AVG
+km/h</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="141"/>
+ <source>AVG
+mph</source>
+ <translation>AVG
+mph</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="193"/>
+ <source>km
+assist
+left</source>
+ <translation>km
+assist
+left</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="193"/>
+ <source>mi.
+assist
+left</source>
+ <translation>mi.
+assist
+left</translation>
+ </message>
+</context>
+<context>
+ <name>StatsPage</name>
+ <message>
+ <location filename="StatsPage.qml" line="94"/>
+ <source>END TRIP</source>
+ <translation>END TRIP</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="113"/>
+ <source>YESTERDAY</source>
+ <translation>YESTERDAY</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="198"/>
+ <source>Total</source>
+ <translation>Total</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="225"/>
+ <source>km</source>
+ <translation>km</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="225"/>
+ <source>mi.</source>
+ <translation>mi.</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="265"/>
+ <source>Duration (h:m)</source>
+ <translation>Duration (h:m)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="267"/>
+ <source>Max. speed (km/h)</source>
+ <translation>Max. speed (km/h)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="267"/>
+ <source>Max. speed (mph)</source>
+ <translation>Max. speed (mph)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="276"/>
+ <source>Distance (km)</source>
+ <translation>Distance (km)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="276"/>
+ <source>Distance (mi.)</source>
+ <translation>Distance (mi.)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="278"/>
+ <source>Avg. speed (km/h)</source>
+ <translation>Avg. speed (km/h)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="278"/>
+ <source>Avg. speed (mph)</source>
+ <translation>Avg. speed (mph)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="287"/>
+ <source>Calories (kcal)</source>
+ <translation>Calories (kcal)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="289"/>
+ <source>Ascent (m)</source>
+ <translation>Ascent (m)</translation>
+ </message>
+</context>
+<context>
+ <name>SuggestionsModel</name>
+ <message>
+ <location filename="suggestionsmodel.cpp" line="67"/>
+ <source>Place</source>
+ <translation>Place</translation>
+ </message>
+</context>
+<context>
+ <name>TripChart</name>
+ <message>
+ <location filename="TripChart.qml" line="112"/>
+ <source>Speed (km/h)</source>
+ <translation>Speed (km/h)</translation>
+ </message>
+ <message>
+ <location filename="TripChart.qml" line="112"/>
+ <source>Speed (mph)</source>
+ <translation>Speed (mph)</translation>
+ </message>
+ <message>
+ <location filename="TripChart.qml" line="128"/>
+ <source>Pedal assist (W)</source>
+ <translation>Pedal assist (W)</translation>
+ </message>
+</context>
+<context>
+ <name>ViewTab</name>
+ <message>
+ <location filename="ViewTab.qml" line="51"/>
+ <source>VIEW</source>
+ <translation>VIEW</translation>
+ </message>
+ <message>
+ <location filename="ViewTab.qml" line="66"/>
+ <source>Show FPS</source>
+ <translation>Show FPS</translation>
+ </message>
+ <message>
+ <location filename="ViewTab.qml" line="84"/>
+ <source>Audio controls</source>
+ <translation>Audio controls</translation>
+ </message>
+ <message>
+ <location filename="ViewTab.qml" line="102"/>
+ <source>TBD</source>
+ <translation>TBD</translation>
+ </message>
+</context>
+<context>
+ <name>mainview</name>
+ <message>
+ <location filename="mainview.qml" line="50"/>
+ <source>Qt eBike</source>
+ <translation>Qt eBike</translation>
+ </message>
+</context>
+</TS>
diff --git a/basicsuite/ebike-ui/ebike_fi.ts b/basicsuite/ebike-ui/ebike_fi.ts
new file mode 100644
index 0000000..9601fc7
--- /dev/null
+++ b/basicsuite/ebike-ui/ebike_fi.ts
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="fi_FI">
+<context>
+ <name>BikeInfoTab</name>
+ <message>
+ <location filename="BikeInfoTab.qml" line="52"/>
+ <source>BIKE INFO</source>
+ <translation>PYÖRÄN TIEDOT</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="67"/>
+ <source>Next maintenance</source>
+ <translation>Seuraava huolto</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="75"/>
+ <source>15000 km</source>
+ <translation>15000 km</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="75"/>
+ <source>10000 mi.</source>
+ <translation>10000 mi.</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="91"/>
+ <source>Battery health</source>
+ <translation>Akun tila</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="99"/>
+ <source>Excellent</source>
+ <translation>Loistava</translation>
+ </message>
+ <message>
+ <location filename="BikeInfoTab.qml" line="115"/>
+ <source>System Log</source>
+ <translation>Järjestelmälogi</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurationDrawer</name>
+ <message>
+ <source>LIST</source>
+ <translation type="vanished">LISTA</translation>
+ </message>
+ <message>
+ <source>TBD</source>
+ <translation type="vanished">MYÖH</translation>
+ </message>
+</context>
+<context>
+ <name>GeneralTab</name>
+ <message>
+ <location filename="GeneralTab.qml" line="52"/>
+ <source>GENERAL</source>
+ <translation>YLEINEN</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="67"/>
+ <source>Language</source>
+ <translation>Kieli</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="75"/>
+ <source>English</source>
+ <translation>englanti</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="91"/>
+ <source>Brightness</source>
+ <translation>Kirkkaus</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="99"/>
+ <source>Auto</source>
+ <translation>Auto</translation>
+ </message>
+ <message>
+ <location filename="GeneralTab.qml" line="201"/>
+ <source>Units</source>
+ <translation>Yksiköt</translation>
+ </message>
+</context>
+<context>
+ <name>ModeBox</name>
+ <message>
+ <location filename="ModeBox.qml" line="62"/>
+ <source>SPORT</source>
+ <translation>SPORT</translation>
+ </message>
+ <message>
+ <location filename="ModeBox.qml" line="78"/>
+ <source>CRUISE</source>
+ <translation>CRUISE</translation>
+ </message>
+</context>
+<context>
+ <name>MusicPlayer</name>
+ <message>
+ <location filename="MusicPlayer.qml" line="133"/>
+ <source>Singer McSongface - Some really long title here</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>NaviPage</name>
+ <message>
+ <location filename="NaviPage.qml" line="89"/>
+ <source>&lt;i&gt;Where do you want to go?&lt;/i&gt;</source>
+ <translation>&lt;i&gt;Mihin haluat mennä?&lt;/i&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>SpeedView</name>
+ <message>
+ <location filename="SpeedView.qml" line="102"/>
+ <source>km/h</source>
+ <translation>km/h</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="102"/>
+ <source>mph</source>
+ <translation>mph</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="141"/>
+ <source>AVG
+km/h</source>
+ <translation>KESK
+km/h</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="141"/>
+ <source>AVG
+mph</source>
+ <translation>KESK
+mph</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="193"/>
+ <source>km
+assist
+left</source>
+ <translation>km
+avustus
+jäljellä</translation>
+ </message>
+ <message>
+ <location filename="SpeedView.qml" line="193"/>
+ <source>mi.
+assist
+left</source>
+ <translation>mi.
+avustus
+jäljellä</translation>
+ </message>
+</context>
+<context>
+ <name>StatsPage</name>
+ <message>
+ <location filename="StatsPage.qml" line="94"/>
+ <source>END TRIP</source>
+ <translation>LOPETA MATKA</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="113"/>
+ <source>YESTERDAY</source>
+ <translation>EILEN</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="198"/>
+ <source>Total</source>
+ <translation>Yhteensä</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="225"/>
+ <source>km</source>
+ <translation>km</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="225"/>
+ <source>mi.</source>
+ <translation>mi.</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="265"/>
+ <source>Duration (h:m)</source>
+ <translation>Kesto (h:m)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="267"/>
+ <source>Max. speed (km/h)</source>
+ <translation>Huippunopeus (km/h)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="267"/>
+ <source>Max. speed (mph)</source>
+ <translation>Huippunopeus (mph)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="276"/>
+ <source>Distance (km)</source>
+ <translation>Etäisyys (km)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="276"/>
+ <source>Distance (mi.)</source>
+ <translation>Etäisyys (mi.)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="278"/>
+ <source>Avg. speed (km/h)</source>
+ <translation>Keskinopeus (km/h)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="278"/>
+ <source>Avg. speed (mph)</source>
+ <translation>Keskinopeus (mph)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="287"/>
+ <source>Calories (kcal)</source>
+ <translation>Kalorit (kcal)</translation>
+ </message>
+ <message>
+ <location filename="StatsPage.qml" line="289"/>
+ <source>Ascent (m)</source>
+ <translation>Nousu (m)</translation>
+ </message>
+</context>
+<context>
+ <name>SuggestionsModel</name>
+ <message>
+ <location filename="suggestionsmodel.cpp" line="67"/>
+ <source>Place</source>
+ <translation>Paikka</translation>
+ </message>
+</context>
+<context>
+ <name>TripChart</name>
+ <message>
+ <location filename="TripChart.qml" line="112"/>
+ <source>Speed (km/h)</source>
+ <translation>Nopeus (km/h)</translation>
+ </message>
+ <message>
+ <location filename="TripChart.qml" line="112"/>
+ <source>Speed (mph)</source>
+ <translation>Nopeus (mph)</translation>
+ </message>
+ <message>
+ <location filename="TripChart.qml" line="128"/>
+ <source>Pedal assist (W)</source>
+ <translation>Avustusteho (W)</translation>
+ </message>
+</context>
+<context>
+ <name>ViewTab</name>
+ <message>
+ <location filename="ViewTab.qml" line="51"/>
+ <source>VIEW</source>
+ <translation>NÄYTÄ</translation>
+ </message>
+ <message>
+ <location filename="ViewTab.qml" line="66"/>
+ <source>Show FPS</source>
+ <translation>Näytä FPS</translation>
+ </message>
+ <message>
+ <location filename="ViewTab.qml" line="84"/>
+ <source>Audio controls</source>
+ <translation>Musiikkisoitin</translation>
+ </message>
+ <message>
+ <location filename="ViewTab.qml" line="102"/>
+ <source>TBD</source>
+ <translation>MYÖH</translation>
+ </message>
+</context>
+<context>
+ <name>mainview</name>
+ <message>
+ <location filename="mainview.qml" line="50"/>
+ <source>Qt eBike</source>
+ <translation>Qt eBike</translation>
+ </message>
+</context>
+</TS>
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-Black.ttf b/basicsuite/ebike-ui/fonts/Montserrat-Black.ttf
new file mode 100644
index 0000000..bf5443c
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-Black.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-BlackItalic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-BlackItalic.ttf
new file mode 100644
index 0000000..3eee3a7
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-BlackItalic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-Bold.ttf b/basicsuite/ebike-ui/fonts/Montserrat-Bold.ttf
new file mode 100644
index 0000000..8e9a5f3
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-Bold.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-BoldItalic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-BoldItalic.ttf
new file mode 100644
index 0000000..2c33630
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-BoldItalic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-ExtraBold.ttf b/basicsuite/ebike-ui/fonts/Montserrat-ExtraBold.ttf
new file mode 100644
index 0000000..1e3692d
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-ExtraBold.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-ExtraBoldItalic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..5f6c382
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-ExtraBoldItalic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-ExtraLight.ttf b/basicsuite/ebike-ui/fonts/Montserrat-ExtraLight.ttf
new file mode 100644
index 0000000..7490dc7
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-ExtraLight.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-ExtraLightItalic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-ExtraLightItalic.ttf
new file mode 100644
index 0000000..24e1354
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-ExtraLightItalic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-Italic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-Italic.ttf
new file mode 100644
index 0000000..c5a36e5
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-Italic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-Light.ttf b/basicsuite/ebike-ui/fonts/Montserrat-Light.ttf
new file mode 100644
index 0000000..e66dc5b
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-Light.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-LightItalic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-LightItalic.ttf
new file mode 100644
index 0000000..b78b8b7
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-LightItalic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-Medium.ttf b/basicsuite/ebike-ui/fonts/Montserrat-Medium.ttf
new file mode 100644
index 0000000..88d70b8
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-Medium.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-MediumItalic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-MediumItalic.ttf
new file mode 100644
index 0000000..225fd18
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-MediumItalic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-Regular.ttf b/basicsuite/ebike-ui/fonts/Montserrat-Regular.ttf
new file mode 100644
index 0000000..626355a
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-Regular.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-SemiBold.ttf b/basicsuite/ebike-ui/fonts/Montserrat-SemiBold.ttf
new file mode 100644
index 0000000..6157045
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-SemiBold.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-SemiBoldItalic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-SemiBoldItalic.ttf
new file mode 100644
index 0000000..c6dd977
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-SemiBoldItalic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-Thin.ttf b/basicsuite/ebike-ui/fonts/Montserrat-Thin.ttf
new file mode 100644
index 0000000..dc16a02
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-Thin.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Montserrat-ThinItalic.ttf b/basicsuite/ebike-ui/fonts/Montserrat-ThinItalic.ttf
new file mode 100644
index 0000000..b9e12f4
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Montserrat-ThinItalic.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/OFL.txt b/basicsuite/ebike-ui/fonts/OFL.txt
new file mode 100644
index 0000000..74605df
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com).
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/basicsuite/ebike-ui/fonts/Teko-Bold.ttf b/basicsuite/ebike-ui/fonts/Teko-Bold.ttf
new file mode 100644
index 0000000..d061824
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Teko-Bold.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Teko-Light.ttf b/basicsuite/ebike-ui/fonts/Teko-Light.ttf
new file mode 100644
index 0000000..ec5194a
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Teko-Light.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Teko-Medium.ttf b/basicsuite/ebike-ui/fonts/Teko-Medium.ttf
new file mode 100644
index 0000000..cc38086
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Teko-Medium.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Teko-Regular.ttf b/basicsuite/ebike-ui/fonts/Teko-Regular.ttf
new file mode 100644
index 0000000..3161e63
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Teko-Regular.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/Teko-SemiBold.ttf b/basicsuite/ebike-ui/fonts/Teko-SemiBold.ttf
new file mode 100644
index 0000000..bc17e5a
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/Teko-SemiBold.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fonts/fontawesome-webfont.ttf b/basicsuite/ebike-ui/fonts/fontawesome-webfont.ttf
new file mode 100644
index 0000000..35acda2
--- /dev/null
+++ b/basicsuite/ebike-ui/fonts/fontawesome-webfont.ttf
Binary files differ
diff --git a/basicsuite/ebike-ui/fpscounter.cpp b/basicsuite/ebike-ui/fpscounter.cpp
new file mode 100644
index 0000000..137dcb4
--- /dev/null
+++ b/basicsuite/ebike-ui/fpscounter.cpp
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QQuickWindow>
+
+#include "fpscounter.h"
+
+FpsCounter::FpsCounter(QObject *parent)
+ : QObject(parent)
+ , m_frameCounter(0)
+ , m_fps(0.0)
+ , m_visible(false)
+{
+}
+
+void FpsCounter::setVisible(bool visible)
+{
+ if (m_visible == visible)
+ return;
+
+ m_visible = visible;
+ emit visibleChanged(m_visible);
+}
+
+void FpsCounter::setWindow(QQuickWindow *window)
+{
+ connect(window, &QQuickWindow::frameSwapped, this, &FpsCounter::frameUpdated);
+ startTimer(1000);
+ m_timer.start();
+}
+
+void FpsCounter::timerEvent(QTimerEvent *)
+{
+ // Calculate new FPS
+ qreal newfps = qRound(m_frameCounter * 1000.0 / m_timer.elapsed());
+ m_frameCounter = 0;
+ m_timer.start();
+
+ // If there is no change, do nothing
+ if (qFuzzyCompare(m_fps, newfps))
+ return;
+
+ // Otherwise emit new fps
+ m_fps = newfps;
+ emit fpsChanged(m_fps);
+}
diff --git a/basicsuite/ebike-ui/fpscounter.h b/basicsuite/ebike-ui/fpscounter.h
new file mode 100644
index 0000000..7828844
--- /dev/null
+++ b/basicsuite/ebike-ui/fpscounter.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 FPSCOUNTER_H
+#define FPSCOUNTER_H
+
+#include <QObject>
+#include <QElapsedTimer>
+
+class QQuickWindow;
+
+class FpsCounter : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal fps READ fps NOTIFY fpsChanged)
+ Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged)
+
+public:
+ explicit FpsCounter(QObject *parent = nullptr);
+
+public:
+ qreal fps() const { return m_fps; }
+ bool visible() const { return m_visible; }
+ void setVisible(bool visible);
+ void setWindow(QQuickWindow *window);
+
+protected:
+ void timerEvent(QTimerEvent *);
+
+signals:
+ void fpsChanged(qreal fps);
+ void visibleChanged(bool visible);
+
+private slots:
+ void frameUpdated() { m_frameCounter++; }
+
+private:
+ QElapsedTimer m_timer;
+ int m_frameCounter;
+ qreal m_fps;
+ bool m_visible;
+};
+
+#endif // FPSCOUNTER_H
diff --git a/basicsuite/ebike-ui/images/arrow_left.png b/basicsuite/ebike-ui/images/arrow_left.png
new file mode 100644
index 0000000..6c67a2c
--- /dev/null
+++ b/basicsuite/ebike-ui/images/arrow_left.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/arrow_right.png b/basicsuite/ebike-ui/images/arrow_right.png
new file mode 100644
index 0000000..b8cfb2e
--- /dev/null
+++ b/basicsuite/ebike-ui/images/arrow_right.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/assist.png b/basicsuite/ebike-ui/images/assist.png
new file mode 100644
index 0000000..4a2bc95
--- /dev/null
+++ b/basicsuite/ebike-ui/images/assist.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/battery.png b/basicsuite/ebike-ui/images/battery.png
new file mode 100644
index 0000000..170c48d
--- /dev/null
+++ b/basicsuite/ebike-ui/images/battery.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/bike-battery.png b/basicsuite/ebike-ui/images/bike-battery.png
new file mode 100644
index 0000000..e354d33
--- /dev/null
+++ b/basicsuite/ebike-ui/images/bike-battery.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/bike-brakes.png b/basicsuite/ebike-ui/images/bike-brakes.png
new file mode 100644
index 0000000..c271d75
--- /dev/null
+++ b/basicsuite/ebike-ui/images/bike-brakes.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/bike-chain.png b/basicsuite/ebike-ui/images/bike-chain.png
new file mode 100644
index 0000000..07879a4
--- /dev/null
+++ b/basicsuite/ebike-ui/images/bike-chain.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/bike-frontwheel.png b/basicsuite/ebike-ui/images/bike-frontwheel.png
new file mode 100644
index 0000000..ab9297f
--- /dev/null
+++ b/basicsuite/ebike-ui/images/bike-frontwheel.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/bike-gears.png b/basicsuite/ebike-ui/images/bike-gears.png
new file mode 100644
index 0000000..c977644
--- /dev/null
+++ b/basicsuite/ebike-ui/images/bike-gears.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/bike-light.png b/basicsuite/ebike-ui/images/bike-light.png
new file mode 100644
index 0000000..b18da3c
--- /dev/null
+++ b/basicsuite/ebike-ui/images/bike-light.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/bike-rearwheel.png b/basicsuite/ebike-ui/images/bike-rearwheel.png
new file mode 100644
index 0000000..fb02923
--- /dev/null
+++ b/basicsuite/ebike-ui/images/bike-rearwheel.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/blue_circle_gps_area.png b/basicsuite/ebike-ui/images/blue_circle_gps_area.png
new file mode 100644
index 0000000..06e1b6e
--- /dev/null
+++ b/basicsuite/ebike-ui/images/blue_circle_gps_area.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/calories.png b/basicsuite/ebike-ui/images/calories.png
new file mode 100644
index 0000000..fe3cbb1
--- /dev/null
+++ b/basicsuite/ebike-ui/images/calories.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/checkmark.png b/basicsuite/ebike-ui/images/checkmark.png
new file mode 100644
index 0000000..0e1b387
--- /dev/null
+++ b/basicsuite/ebike-ui/images/checkmark.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/curtain_shadow_handle.png b/basicsuite/ebike-ui/images/curtain_shadow_handle.png
new file mode 100644
index 0000000..afaf3ec
--- /dev/null
+++ b/basicsuite/ebike-ui/images/curtain_shadow_handle.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/curtain_up_arrow.png b/basicsuite/ebike-ui/images/curtain_up_arrow.png
new file mode 100644
index 0000000..97095d0
--- /dev/null
+++ b/basicsuite/ebike-ui/images/curtain_up_arrow.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/fps_icon.png b/basicsuite/ebike-ui/images/fps_icon.png
new file mode 100644
index 0000000..4cecab7
--- /dev/null
+++ b/basicsuite/ebike-ui/images/fps_icon.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/info.png b/basicsuite/ebike-ui/images/info.png
new file mode 100644
index 0000000..6ad9192
--- /dev/null
+++ b/basicsuite/ebike-ui/images/info.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/info_selected.png b/basicsuite/ebike-ui/images/info_selected.png
new file mode 100644
index 0000000..25056ff
--- /dev/null
+++ b/basicsuite/ebike-ui/images/info_selected.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/lights_off.png b/basicsuite/ebike-ui/images/lights_off.png
new file mode 100644
index 0000000..4ad0abd
--- /dev/null
+++ b/basicsuite/ebike-ui/images/lights_off.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/lights_on.png b/basicsuite/ebike-ui/images/lights_on.png
new file mode 100644
index 0000000..6da9893
--- /dev/null
+++ b/basicsuite/ebike-ui/images/lights_on.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/list.png b/basicsuite/ebike-ui/images/list.png
new file mode 100644
index 0000000..2e1633d
--- /dev/null
+++ b/basicsuite/ebike-ui/images/list.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/list_selected.png b/basicsuite/ebike-ui/images/list_selected.png
new file mode 100644
index 0000000..1c61a5e
--- /dev/null
+++ b/basicsuite/ebike-ui/images/list_selected.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/map-marker.png b/basicsuite/ebike-ui/images/map-marker.png
new file mode 100644
index 0000000..e2dd669
--- /dev/null
+++ b/basicsuite/ebike-ui/images/map-marker.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/map_btn_shadow.png b/basicsuite/ebike-ui/images/map_btn_shadow.png
new file mode 100644
index 0000000..c40b9b1
--- /dev/null
+++ b/basicsuite/ebike-ui/images/map_btn_shadow.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/map_destination.png b/basicsuite/ebike-ui/images/map_destination.png
new file mode 100644
index 0000000..a41b134
--- /dev/null
+++ b/basicsuite/ebike-ui/images/map_destination.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/map_locate.png b/basicsuite/ebike-ui/images/map_locate.png
new file mode 100644
index 0000000..2579c94
--- /dev/null
+++ b/basicsuite/ebike-ui/images/map_locate.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/map_location_arrow.png b/basicsuite/ebike-ui/images/map_location_arrow.png
new file mode 100644
index 0000000..9ec4d44
--- /dev/null
+++ b/basicsuite/ebike-ui/images/map_location_arrow.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/map_zoomin.png b/basicsuite/ebike-ui/images/map_zoomin.png
new file mode 100644
index 0000000..aea24fe
--- /dev/null
+++ b/basicsuite/ebike-ui/images/map_zoomin.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/map_zoomout.png b/basicsuite/ebike-ui/images/map_zoomout.png
new file mode 100644
index 0000000..e3f90da
--- /dev/null
+++ b/basicsuite/ebike-ui/images/map_zoomout.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_bear_l.png b/basicsuite/ebike-ui/images/nav_bear_l.png
new file mode 100644
index 0000000..b078fbf
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_bear_l.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_bear_r.png b/basicsuite/ebike-ui/images/nav_bear_r.png
new file mode 100644
index 0000000..84b48c1
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_bear_r.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_hard_l.png b/basicsuite/ebike-ui/images/nav_hard_l.png
new file mode 100644
index 0000000..7235cc3
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_hard_l.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_hard_r.png b/basicsuite/ebike-ui/images/nav_hard_r.png
new file mode 100644
index 0000000..4a19b65
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_hard_r.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_left.png b/basicsuite/ebike-ui/images/nav_left.png
new file mode 100644
index 0000000..5a5aa26
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_left.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_light_left.png b/basicsuite/ebike-ui/images/nav_light_left.png
new file mode 100644
index 0000000..7c88914
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_light_left.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_light_right.png b/basicsuite/ebike-ui/images/nav_light_right.png
new file mode 100644
index 0000000..3bb07ff
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_light_right.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_nodir.png b/basicsuite/ebike-ui/images/nav_nodir.png
new file mode 100644
index 0000000..7fc92a7
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_nodir.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_right.png b/basicsuite/ebike-ui/images/nav_right.png
new file mode 100644
index 0000000..8eecf13
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_right.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_straight.png b/basicsuite/ebike-ui/images/nav_straight.png
new file mode 100644
index 0000000..44a58c8
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_straight.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_uturn_l.png b/basicsuite/ebike-ui/images/nav_uturn_l.png
new file mode 100644
index 0000000..c9714d3
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_uturn_l.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nav_uturn_r.png b/basicsuite/ebike-ui/images/nav_uturn_r.png
new file mode 100644
index 0000000..a4aee58
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nav_uturn_r.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/navigation_widget_shadow.png b/basicsuite/ebike-ui/images/navigation_widget_shadow.png
new file mode 100644
index 0000000..4de81eb
--- /dev/null
+++ b/basicsuite/ebike-ui/images/navigation_widget_shadow.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nextsong.png b/basicsuite/ebike-ui/images/nextsong.png
new file mode 100644
index 0000000..90d6520
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nextsong.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/nextsong_pressed.png b/basicsuite/ebike-ui/images/nextsong_pressed.png
new file mode 100644
index 0000000..b764100
--- /dev/null
+++ b/basicsuite/ebike-ui/images/nextsong_pressed.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/ok.png b/basicsuite/ebike-ui/images/ok.png
new file mode 100644
index 0000000..6d862a4
--- /dev/null
+++ b/basicsuite/ebike-ui/images/ok.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/pause.png b/basicsuite/ebike-ui/images/pause.png
new file mode 100644
index 0000000..9425527
--- /dev/null
+++ b/basicsuite/ebike-ui/images/pause.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/pause_pressed.png b/basicsuite/ebike-ui/images/pause_pressed.png
new file mode 100644
index 0000000..b90e029
--- /dev/null
+++ b/basicsuite/ebike-ui/images/pause_pressed.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/placeholder.png b/basicsuite/ebike-ui/images/placeholder.png
new file mode 100644
index 0000000..a0e647e
--- /dev/null
+++ b/basicsuite/ebike-ui/images/placeholder.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/play.png b/basicsuite/ebike-ui/images/play.png
new file mode 100644
index 0000000..d31ae43
--- /dev/null
+++ b/basicsuite/ebike-ui/images/play.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/play_pressed.png b/basicsuite/ebike-ui/images/play_pressed.png
new file mode 100644
index 0000000..3cd498b
--- /dev/null
+++ b/basicsuite/ebike-ui/images/play_pressed.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/prevsong.png b/basicsuite/ebike-ui/images/prevsong.png
new file mode 100644
index 0000000..63d2619
--- /dev/null
+++ b/basicsuite/ebike-ui/images/prevsong.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/prevsong_pressed.png b/basicsuite/ebike-ui/images/prevsong_pressed.png
new file mode 100644
index 0000000..dbe5690
--- /dev/null
+++ b/basicsuite/ebike-ui/images/prevsong_pressed.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/search.png b/basicsuite/ebike-ui/images/search.png
new file mode 100644
index 0000000..f840e72
--- /dev/null
+++ b/basicsuite/ebike-ui/images/search.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/search_cancel.png b/basicsuite/ebike-ui/images/search_cancel.png
new file mode 100644
index 0000000..679ce32
--- /dev/null
+++ b/basicsuite/ebike-ui/images/search_cancel.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/settings.png b/basicsuite/ebike-ui/images/settings.png
new file mode 100644
index 0000000..9651de6
--- /dev/null
+++ b/basicsuite/ebike-ui/images/settings.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/settings_selected.png b/basicsuite/ebike-ui/images/settings_selected.png
new file mode 100644
index 0000000..2e8cd7f
--- /dev/null
+++ b/basicsuite/ebike-ui/images/settings_selected.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/small_input_box_shadow.png b/basicsuite/ebike-ui/images/small_input_box_shadow.png
new file mode 100644
index 0000000..401c0f2
--- /dev/null
+++ b/basicsuite/ebike-ui/images/small_input_box_shadow.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/small_speedometer_arrow.png b/basicsuite/ebike-ui/images/small_speedometer_arrow.png
new file mode 100644
index 0000000..4959e8d
--- /dev/null
+++ b/basicsuite/ebike-ui/images/small_speedometer_arrow.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/small_speedometer_shadow.png b/basicsuite/ebike-ui/images/small_speedometer_shadow.png
new file mode 100644
index 0000000..5b37857
--- /dev/null
+++ b/basicsuite/ebike-ui/images/small_speedometer_shadow.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/speed.png b/basicsuite/ebike-ui/images/speed.png
new file mode 100644
index 0000000..17d2e6d
--- /dev/null
+++ b/basicsuite/ebike-ui/images/speed.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/spinner.png b/basicsuite/ebike-ui/images/spinner.png
new file mode 100644
index 0000000..e59efb2
--- /dev/null
+++ b/basicsuite/ebike-ui/images/spinner.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/top_curtain_drag.png b/basicsuite/ebike-ui/images/top_curtain_drag.png
new file mode 100644
index 0000000..28388c6
--- /dev/null
+++ b/basicsuite/ebike-ui/images/top_curtain_drag.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/trip.png b/basicsuite/ebike-ui/images/trip.png
new file mode 100644
index 0000000..5a1761c
--- /dev/null
+++ b/basicsuite/ebike-ui/images/trip.png
Binary files differ
diff --git a/basicsuite/ebike-ui/images/warning.png b/basicsuite/ebike-ui/images/warning.png
new file mode 100644
index 0000000..0c37552
--- /dev/null
+++ b/basicsuite/ebike-ui/images/warning.png
Binary files differ
diff --git a/basicsuite/ebike-ui/main.qml b/basicsuite/ebike-ui/main.qml
new file mode 100644
index 0000000..33fe5e8
--- /dev/null
+++ b/basicsuite/ebike-ui/main.qml
@@ -0,0 +1,350 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import QtQuick.VirtualKeyboard 2.1
+import QtQml 2.2
+import DataStore 1.0
+
+import "./BikeStyle"
+
+Rectangle {
+ visible: true
+ width: 640
+ height: 480
+ color: "#111520"
+ id: root
+
+ // Permanent placeholder for time display
+ ClockView {
+ id: clockButton
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+
+ MouseArea {
+ anchors.rightMargin: -20
+ anchors.leftMargin: -20
+ anchors.bottomMargin: -20
+ anchors.fill: parent
+ onClicked: drawer.open()
+ }
+ }
+
+ FpsItem {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ visible: fps.visible
+ }
+
+ // The always-visible speed view
+ SpeedView {
+ id: speedView
+ onShowMain: swipeView.currentIndex = 1
+ showZero: naviPage.targetEdit.activeFocus
+
+ states: [
+ State {
+ name: ""
+ when: swipeView.currentIndex == 1 && (!speedView.enlarged)
+ PropertyChanges {
+ target: speedView
+ width: UILayout.speedViewRadius * 2 + 2
+ height: UILayout.speedViewRadius * 2 + 2
+ anchors.topMargin: UILayout.speedViewTop
+ anchors.leftMargin: 0
+ anchors.bottomMargin: 0
+ color: "transparent"
+ dotcount: UILayout.speedViewDots
+ curvewidth: UILayout.speedViewInnerWidth
+ speedTextSize: UILayout.speedTextSize
+ speedBaselineOffset: UILayout.speedBaselineOffset + 1
+ innerRadius: UILayout.speedViewInnerRadius
+ speedUnitsSize: UILayout.speedUnitsSize
+ speedUnitBaselineOffset: UILayout.speedTextUnitMargin
+ speedIconsOffset: UILayout.speedIconsCenterOffset
+ speedInfoTextsOffset: 0
+ speedInfoTextsSize: UILayout.speedInfoTextsSize
+ speedInfoUnitsOffset: UILayout.speedInfoUnitsOffset
+ assistPowerIconOffset: UILayout.assistPowerIconOffset
+ }
+ AnchorChanges {
+ target: speedView
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.left: undefined
+ anchors.bottom: undefined
+ }
+ PropertyChanges {
+ target: speedView.cornerRectangle
+ color: "transparent"
+ }
+ AnchorChanges {
+ target: speedView.cornerRectangle
+ anchors.horizontalCenter: speedView.horizontalCenter
+ anchors.verticalCenter: speedView.verticalCenter
+ anchors.left: undefined
+ anchors.bottom: undefined
+ }
+ StateChangeScript {
+ script: {
+ if (musicPlayer.lastMusicPlayerState === "" && (!drawer.viewTab.musicPlayerSwitch.checked))
+ musicPlayer.state = "";
+ }
+ }
+ },
+ State {
+ name: "CORNERED"
+ when: swipeView.currentIndex != 1
+ PropertyChanges {
+ target: speedView
+ width: UILayout.speedViewRadiusMinified * 2
+ height: UILayout.speedViewRadiusMinified * 2
+ anchors.topMargin: 0
+ anchors.leftMargin: UILayout.speedViewCornerLeftMargin
+ anchors.bottomMargin: UILayout.speedViewCornerBottomMargin
+ color: Colors.speedViewBackgroundCornered
+ dotcount: UILayout.speedViewDotsMinified
+ curvewidth: UILayout.speedViewInnerWidthMinified
+ speedTextSize: UILayout.speedTextSizeMinified
+ speedBaselineOffset: UILayout.speedBaselineOffsetMinified
+ innerRadius: UILayout.speedViewInnerRadiusMinified
+ speedUnitBaselineOffset: UILayout.speedTextUnitMarginMinified
+ }
+ AnchorChanges {
+ target: speedView
+ anchors.horizontalCenter: undefined
+ anchors.top: undefined
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ }
+ PropertyChanges {
+ target: speedView.cornerRectangle
+ color: Colors.speedViewBackgroundCornered
+ }
+ AnchorChanges {
+ target: speedView.cornerRectangle
+ anchors.horizontalCenter: undefined
+ anchors.verticalCenter: undefined
+ anchors.left: speedView.left
+ anchors.bottom: speedView.bottom
+ }
+ StateChangeScript {
+ script: {
+ musicPlayer.lastMusicPlayerState = musicPlayer.state;
+ musicPlayer.state = "hidden";
+ }
+ }
+ },
+ State {
+ name: "ENLARGED"
+ when: swipeView.currentIndex == 1 && speedView.enlarged
+ PropertyChanges {
+ target: speedView
+ width: UILayout.speedViewRadiusEnlarged * 2
+ height: UILayout.speedViewRadiusEnlarged * 2
+ anchors.topMargin: 35
+ dotcount: UILayout.speedViewDotsEnlarged
+ speedTextSize: UILayout.speedTextSizeEnlarged
+ speedBaselineOffset: UILayout.speedBaselineOffsetEnlarged
+ innerRadius: UILayout.speedViewInnerRadiusEnlarged
+ speedUnitsSize: UILayout.speedUnitsSizeEnlarged
+ speedUnitBaselineOffset: UILayout.speedTextUnitMarginEnlarged
+ speedIconsOffset: UILayout.speedIconsCenterOffsetEnlarged
+ speedInfoTextsOffset: UILayout.speedInfoTextsOffsetEnlarged
+ speedInfoTextsSize: UILayout.speedInfoTextsSizeEnlarged
+ speedInfoUnitsOffset: UILayout.speedInfoUnitsOffsetEnlarged
+ assistPowerIconOffset: UILayout.assistPowerIconOffsetEnlarged
+ }
+ PropertyChanges {
+ target: mainPage.statsButton
+ anchors.leftMargin: -mainPage.statsButton.width
+ anchors.topMargin: -mainPage.statsButton.height
+ }
+ PropertyChanges {
+ target: mainPage.naviButton
+ anchors.rightMargin: -mainPage.naviButton.width
+ anchors.topMargin: -mainPage.naviButton.height
+ }
+ PropertyChanges {
+ target: mainPage.lightsButton
+ anchors.leftMargin: -mainPage.statsButton.width
+ anchors.bottomMargin: -mainPage.statsButton.height
+ }
+ PropertyChanges {
+ target: mainPage.modeButton
+ anchors.rightMargin: -mainPage.statsButton.width
+ anchors.bottomMargin: -mainPage.statsButton.height
+ }
+ PropertyChanges {
+ target: clockButton
+ anchors.topMargin: -clockButton.height
+ }
+ AnchorChanges {
+ target: speedView
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.left: undefined
+ anchors.bottom: undefined
+ }
+ StateChangeScript {
+ script: {
+ musicPlayer.lastMusicPlayerState = musicPlayer.state;
+ musicPlayer.state = "hidden";
+ }
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ from: ""; to: "ENLARGED"
+ NumberAnimation {
+ properties: "anchors.leftMargin,anchors.rightMargin,anchors.topMargin,anchors.bottomMargin"
+ duration: 250
+ }
+ NumberAnimation {
+ properties: "width,height,dotcount,speedTextSize,speedBaselineOffset,innerRadius,speedUnitsSize,speedUnitBaselineOffset,speedIconsOffset,speedInfoTextsOffset,speedInfoTextsSize,speedInfoUnitsOffset,assistPowerIconOffset"
+ duration: 250
+ }
+ AnchorAnimation {
+ duration: 250
+ }
+ },
+ Transition {
+ from: "ENLARGED"; to: ""
+ NumberAnimation {
+ properties: "anchors.leftMargin,anchors.rightMargin,anchors.topMargin,anchors.bottomMargin"
+ easing.type: Easing.OutBack
+ duration: 250
+ }
+ NumberAnimation {
+ properties: "width,height,dotcount,speedTextSize,speedBaselineOffset,innerRadius,speedUnitsSize,speedUnitBaselineOffset,speedIconsOffset,speedInfoTextsOffset,speedInfoTextsSize,speedInfoUnitsOffset,assistPowerIconOffset"
+ duration: 250
+ }
+ AnchorAnimation {
+ duration: 250
+ }
+ },
+ Transition {
+ NumberAnimation {
+ properties: "width,height,curvewidth,speedTextSize,speedBaselineOffset,innerRadius,speedUnitMargin"
+ easing.type: Easing.OutBack
+ duration: 250
+ }
+ ColorAnimation {
+ duration: 250
+ }
+ AnchorAnimation {
+ easing.type: Easing.OutBack
+ duration: 250
+ }
+ }
+ ]
+ }
+
+ // Configuration and settings drawer
+ ConfigurationDrawer {
+ id: drawer
+ height: 350
+ width: root.width
+ edge: Qt.TopEdge
+ dragMargin: 20
+ }
+
+ // Inactive swipe view, for animations
+ SwipeView {
+ id: swipeView
+ anchors.fill: parent
+ currentIndex: 1
+ interactive: false
+
+ // List of pages
+ StatsPage {}
+ MainPage {
+ id: mainPage
+ naviGuideArrowSource: naviPage.naviGuideArrowSource
+ naviGuideDistance: naviPage.naviGuideDistance
+ naviGuideAddress: naviPage.naviGuideAddress
+ }
+ NaviPage {
+ id: naviPage;
+ }
+ }
+
+ // Music player
+ MusicPlayer {
+ id: musicPlayer
+ property string lastMusicPlayerState: "unknown"
+ z: 1
+ }
+
+ Connections {
+ target: datastore
+ onDemoReset: drawer.close()
+ }
+
+ // Virtual keyboard
+ InputPanel {
+ id: inputPanel
+ z: 99
+ x: 0
+ y: root.height
+ width: root.width
+
+ states: State {
+ name: "visible"
+ when: inputPanel.active
+ PropertyChanges {
+ target: inputPanel
+ y: root.height - inputPanel.height
+ }
+ }
+ transitions: Transition {
+ from: ""
+ to: "visible"
+ reversible: true
+ ParallelAnimation {
+ NumberAnimation {
+ properties: "y"
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+ }
+}
diff --git a/basicsuite/ebike-ui/mapbox.cpp b/basicsuite/ebike-ui/mapbox.cpp
new file mode 100644
index 0000000..9d2303d
--- /dev/null
+++ b/basicsuite/ebike-ui/mapbox.cpp
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QNetworkAccessManager>
+#include <QUrlQuery>
+
+#include "mapbox.h"
+
+#define MAPBOX_URL "https://api.mapbox.com/"
+#define MAPBOX_TOKEN "pk.eyJ1IjoibWFwYm94NHF0IiwiYSI6ImNpd3J3eDE0eDEzdm8ydHM3YzhzajlrN2oifQ.keEkjqm79SiFDFjnesTcgQ"
+
+MapBox::MapBox(QObject *parent)
+ : QObject(parent)
+ , m_nam(new QNetworkAccessManager(this))
+{
+}
+
+const QUrl MapBox::createUrl(const QString &path, QUrlQuery params) const
+{
+ // Create URL and set path
+ QUrl url(MAPBOX_URL);
+ url.setPath(path, QUrl::TolerantMode);
+
+ // Add access token to query params
+ params.addQueryItem("access_token", MAPBOX_TOKEN);
+ url.setQuery(params);
+
+ return url;
+}
+
+QNetworkReply *MapBox::get(const QUrl &url) const
+{
+ return m_nam->get(QNetworkRequest(url));
+}
+
+QNetworkReply *MapBox::getGeocoding(const QString &query, const QGeoCoordinate &proximity)
+{
+ QUrlQuery params;
+ params.addQueryItem("autocomplete", "true");
+ params.addQueryItem("limit", "3");
+ if (proximity.isValid())
+ params.addQueryItem("proximity", QString("%1,%2").arg(proximity.longitude()).arg(proximity.latitude()));
+ QUrl url = createUrl(QString("/geocoding/v5/mapbox.places/%1.json").arg(QString(QUrl::toPercentEncoding(query))), params);
+
+ return get(url);
+}
+
+QNetworkReply *MapBox::getDirections(const QGeoCoordinate &source, const QGeoCoordinate &destination, const QString &type)
+{
+ QString where = QString("%1,%2;%3,%4").arg(source.longitude()).arg(source.latitude()).arg(destination.longitude()).arg(destination.latitude());
+ QUrlQuery params;
+ params.addQueryItem("steps", "true");
+ params.addQueryItem("geometries", "geojson");
+ QUrl url = createUrl(QString("/directions/v5/mapbox/%1/%2.json").arg(type).arg(QString(QUrl::toPercentEncoding(where))), params);
+
+ return get(url);
+}
diff --git a/basicsuite/ebike-ui/mapbox.h b/basicsuite/ebike-ui/mapbox.h
new file mode 100644
index 0000000..5bd295f
--- /dev/null
+++ b/basicsuite/ebike-ui/mapbox.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 MAPBOX_H
+#define MAPBOX_H
+
+#include <QObject>
+#include <QUrl>
+#include <QGeoCoordinate>
+
+class QNetworkAccessManager;
+class QNetworkReply;
+
+class MapBox : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit MapBox(QObject *parent = nullptr);
+
+public:
+ const QUrl createUrl(const QString &path, QUrlQuery params) const;
+ QNetworkReply *get(const QUrl &url) const;
+ QNetworkReply *getGeocoding(const QString &query, const QGeoCoordinate &proximity=QGeoCoordinate());
+ QNetworkReply *getDirections(const QGeoCoordinate &source, const QGeoCoordinate &destination, const QString &type=QString("cycling"));
+
+signals:
+
+public slots:
+
+private:
+ QNetworkAccessManager *m_nam;
+};
+
+#endif // MAPBOX_H
diff --git a/basicsuite/ebike-ui/mapboxsuggestions.cpp b/basicsuite/ebike-ui/mapboxsuggestions.cpp
new file mode 100644
index 0000000..3f66766
--- /dev/null
+++ b/basicsuite/ebike-ui/mapboxsuggestions.cpp
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QTimer>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QUrl>
+#include <QUrlQuery>
+#include <QJsonDocument>
+#include <QJsonParseError>
+#include <QJsonObject>
+
+#include "mapboxsuggestions.h"
+#include "mapbox.h"
+#include "suggestionsmodel.h"
+
+MapBoxSuggestions::MapBoxSuggestions(MapBox *mapbox, QObject *parent)
+ : QObject(parent)
+ , m_mapbox(mapbox)
+ , m_timer(new QTimer(this))
+ , m_suggestions(new SuggestionsModel(this))
+{
+ // Setup timer to request 500ms after user stops typing
+ m_timer->setSingleShot(true);
+ m_timer->setInterval(500);
+
+ // Connect timer signal
+ connect(m_timer, &QTimer::timeout, this, &MapBoxSuggestions::loadSuggestions);
+}
+
+void MapBoxSuggestions::stopSuggest()
+{
+ m_timer->stop();
+}
+
+void MapBoxSuggestions::setSearch(const QString &search)
+{
+ if (m_search == search)
+ return;
+
+ m_search = search;
+ m_timer->start();
+ emit searchChanged(m_search);
+}
+
+void MapBoxSuggestions::loadSuggestions()
+{
+ QNetworkReply *reply = m_mapbox->getGeocoding(m_search, m_center);
+ connect(reply, &QNetworkReply::finished, this, &MapBoxSuggestions::handleReply);
+ m_requests.append(reply);
+ emit loadingChanged();
+}
+
+void MapBoxSuggestions::handleReply()
+{
+ QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error);
+ if (error.error == QJsonParseError::NoError) {
+ QJsonObject obj = doc.object();
+ m_suggestions->setSuggestions(obj.value("features").toArray());
+ emit suggestionsChanged();
+ }
+
+ m_requests.removeOne(reply);
+ emit loadingChanged();
+ reply->deleteLater();
+}
+
+void MapBoxSuggestions::setCenter(const QGeoCoordinate &center)
+{
+ if (m_center != center) {
+ m_center = center;
+ emit centerChanged();
+ }
+}
diff --git a/basicsuite/ebike-ui/mapboxsuggestions.h b/basicsuite/ebike-ui/mapboxsuggestions.h
new file mode 100644
index 0000000..8dc1506
--- /dev/null
+++ b/basicsuite/ebike-ui/mapboxsuggestions.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 MAPBOXSUGGESTIONS_H
+#define MAPBOXSUGGESTIONS_H
+
+#include <QObject>
+#include <QGeoCoordinate>
+
+class MapBox;
+class QNetworkReply;
+class QTimer;
+class SuggestionsModel;
+
+class MapBoxSuggestions : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(SuggestionsModel suggestions READ suggestions NOTIFY suggestionsChanged)
+ Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
+ Q_PROPERTY(QGeoCoordinate center READ center WRITE setCenter NOTIFY centerChanged)
+ Q_PROPERTY(QString search READ search WRITE setSearch NOTIFY searchChanged)
+
+public:
+ explicit MapBoxSuggestions(MapBox *mapbox, QObject *parent = nullptr);
+
+public:
+ SuggestionsModel *suggestions() const { return m_suggestions; }
+ bool loading() const { return m_requests.size() > 0; }
+ const QGeoCoordinate center() { return m_center; }
+ void setCenter(const QGeoCoordinate &center);
+ const QString search() const { return m_search; }
+ void setSearch(const QString &search);
+
+signals:
+ void suggestionsChanged();
+ void loadingChanged();
+ void centerChanged();
+ void searchChanged(QString search);
+
+public slots:
+ void stopSuggest();
+
+private slots:
+ void loadSuggestions();
+ void handleReply();
+
+private:
+ MapBox *m_mapbox;
+ QTimer *m_timer;
+ QList<QNetworkReply *> m_requests;
+ SuggestionsModel *m_suggestions;
+ QGeoCoordinate m_center;
+ QString m_search;
+};
+
+#endif // MAPBOXSUGGESTIONS_H
diff --git a/basicsuite/ebike-ui/moment.js b/basicsuite/ebike-ui/moment.js
new file mode 100644
index 0000000..eaf827d
--- /dev/null
+++ b/basicsuite/ebike-ui/moment.js
@@ -0,0 +1,4551 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//! moment.js
+//! version : 2.19.2
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+
+;(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ global.moment = factory()
+}(this, (function () { 'use strict';
+
+var hookCallback;
+
+function hooks () {
+ return hookCallback.apply(null, arguments);
+}
+
+// This is done to register the method called with moment()
+// without creating circular dependencies.
+function setHookCallback (callback) {
+ hookCallback = callback;
+}
+
+function isArray(input) {
+ return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
+}
+
+function isObject(input) {
+ // IE8 will treat undefined and null as object if it wasn't for
+ // input != null
+ return input != null && Object.prototype.toString.call(input) === '[object Object]';
+}
+
+function isObjectEmpty(obj) {
+ if (Object.getOwnPropertyNames) {
+ return (Object.getOwnPropertyNames(obj).length === 0);
+ } else {
+ var k;
+ for (k in obj) {
+ if (obj.hasOwnProperty(k)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+function isUndefined(input) {
+ return input === void 0;
+}
+
+function isNumber(input) {
+ return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
+}
+
+function isDate(input) {
+ return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
+}
+
+function map(arr, fn) {
+ var res = [], i;
+ for (i = 0; i < arr.length; ++i) {
+ res.push(fn(arr[i], i));
+ }
+ return res;
+}
+
+function hasOwnProp(a, b) {
+ return Object.prototype.hasOwnProperty.call(a, b);
+}
+
+function extend(a, b) {
+ for (var i in b) {
+ if (hasOwnProp(b, i)) {
+ a[i] = b[i];
+ }
+ }
+
+ if (hasOwnProp(b, 'toString')) {
+ a.toString = b.toString;
+ }
+
+ if (hasOwnProp(b, 'valueOf')) {
+ a.valueOf = b.valueOf;
+ }
+
+ return a;
+}
+
+function createUTC (input, format, locale, strict) {
+ return createLocalOrUTC(input, format, locale, strict, true).utc();
+}
+
+function defaultParsingFlags() {
+ // We need to deep clone this object.
+ return {
+ empty : false,
+ unusedTokens : [],
+ unusedInput : [],
+ overflow : -2,
+ charsLeftOver : 0,
+ nullInput : false,
+ invalidMonth : null,
+ invalidFormat : false,
+ userInvalidated : false,
+ iso : false,
+ parsedDateParts : [],
+ meridiem : null,
+ rfc2822 : false,
+ weekdayMismatch : false
+ };
+}
+
+function getParsingFlags(m) {
+ if (m._pf == null) {
+ m._pf = defaultParsingFlags();
+ }
+ return m._pf;
+}
+
+var some;
+if (Array.prototype.some) {
+ some = Array.prototype.some;
+} else {
+ some = function (fun) {
+ var t = Object(this);
+ var len = t.length >>> 0;
+
+ for (var i = 0; i < len; i++) {
+ if (i in t && fun.call(this, t[i], i, t)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+}
+
+function isValid(m) {
+ if (m._isValid == null) {
+ var flags = getParsingFlags(m);
+ var parsedParts = some.call(flags.parsedDateParts, function (i) {
+ return i != null;
+ });
+ var isNowValid = !isNaN(m._d.getTime()) &&
+ flags.overflow < 0 &&
+ !flags.empty &&
+ !flags.invalidMonth &&
+ !flags.invalidWeekday &&
+ !flags.weekdayMismatch &&
+ !flags.nullInput &&
+ !flags.invalidFormat &&
+ !flags.userInvalidated &&
+ (!flags.meridiem || (flags.meridiem && parsedParts));
+
+ if (m._strict) {
+ isNowValid = isNowValid &&
+ flags.charsLeftOver === 0 &&
+ flags.unusedTokens.length === 0 &&
+ flags.bigHour === undefined;
+ }
+
+ if (Object.isFrozen == null || !Object.isFrozen(m)) {
+ m._isValid = isNowValid;
+ }
+ else {
+ return isNowValid;
+ }
+ }
+ return m._isValid;
+}
+
+function createInvalid (flags) {
+ var m = createUTC(NaN);
+ if (flags != null) {
+ extend(getParsingFlags(m), flags);
+ }
+ else {
+ getParsingFlags(m).userInvalidated = true;
+ }
+
+ return m;
+}
+
+// Plugins that add properties should also add the key here (null value),
+// so we can properly clone ourselves.
+var momentProperties = hooks.momentProperties = [];
+
+function copyConfig(to, from) {
+ var i, prop, val;
+
+ if (!isUndefined(from._isAMomentObject)) {
+ to._isAMomentObject = from._isAMomentObject;
+ }
+ if (!isUndefined(from._i)) {
+ to._i = from._i;
+ }
+ if (!isUndefined(from._f)) {
+ to._f = from._f;
+ }
+ if (!isUndefined(from._l)) {
+ to._l = from._l;
+ }
+ if (!isUndefined(from._strict)) {
+ to._strict = from._strict;
+ }
+ if (!isUndefined(from._tzm)) {
+ to._tzm = from._tzm;
+ }
+ if (!isUndefined(from._isUTC)) {
+ to._isUTC = from._isUTC;
+ }
+ if (!isUndefined(from._offset)) {
+ to._offset = from._offset;
+ }
+ if (!isUndefined(from._pf)) {
+ to._pf = getParsingFlags(from);
+ }
+ if (!isUndefined(from._locale)) {
+ to._locale = from._locale;
+ }
+
+ if (momentProperties.length > 0) {
+ for (i = 0; i < momentProperties.length; i++) {
+ prop = momentProperties[i];
+ val = from[prop];
+ if (!isUndefined(val)) {
+ to[prop] = val;
+ }
+ }
+ }
+
+ return to;
+}
+
+var updateInProgress = false;
+
+// Moment prototype object
+function Moment(config) {
+ copyConfig(this, config);
+ this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+ if (!this.isValid()) {
+ this._d = new Date(NaN);
+ }
+ // Prevent infinite loop in case updateOffset creates new moment
+ // objects.
+ if (updateInProgress === false) {
+ updateInProgress = true;
+ hooks.updateOffset(this);
+ updateInProgress = false;
+ }
+}
+
+function isMoment (obj) {
+ return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
+}
+
+function absFloor (number) {
+ if (number < 0) {
+ // -0 -> 0
+ return Math.ceil(number) || 0;
+ } else {
+ return Math.floor(number);
+ }
+}
+
+function toInt(argumentForCoercion) {
+ var coercedNumber = +argumentForCoercion,
+ value = 0;
+
+ if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+ value = absFloor(coercedNumber);
+ }
+
+ return value;
+}
+
+// compare two arrays, return the number of differences
+function compareArrays(array1, array2, dontConvert) {
+ var len = Math.min(array1.length, array2.length),
+ lengthDiff = Math.abs(array1.length - array2.length),
+ diffs = 0,
+ i;
+ for (i = 0; i < len; i++) {
+ if ((dontConvert && array1[i] !== array2[i]) ||
+ (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
+ diffs++;
+ }
+ }
+ return diffs + lengthDiff;
+}
+
+function warn(msg) {
+ if (hooks.suppressDeprecationWarnings === false &&
+ (typeof console !== 'undefined') && console.warn) {
+ console.warn('Deprecation warning: ' + msg);
+ }
+}
+
+function deprecate(msg, fn) {
+ var firstTime = true;
+
+ return extend(function () {
+ if (hooks.deprecationHandler != null) {
+ hooks.deprecationHandler(null, msg);
+ }
+ if (firstTime) {
+ var args = [];
+ var arg;
+ for (var i = 0; i < arguments.length; i++) {
+ arg = '';
+ if (typeof arguments[i] === 'object') {
+ arg += '\n[' + i + '] ';
+ for (var key in arguments[0]) {
+ arg += key + ': ' + arguments[0][key] + ', ';
+ }
+ arg = arg.slice(0, -2); // Remove trailing comma and space
+ } else {
+ arg = arguments[i];
+ }
+ args.push(arg);
+ }
+ warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
+ firstTime = false;
+ }
+ return fn.apply(this, arguments);
+ }, fn);
+}
+
+var deprecations = {};
+
+function deprecateSimple(name, msg) {
+ if (hooks.deprecationHandler != null) {
+ hooks.deprecationHandler(name, msg);
+ }
+ if (!deprecations[name]) {
+ warn(msg);
+ deprecations[name] = true;
+ }
+}
+
+hooks.suppressDeprecationWarnings = false;
+hooks.deprecationHandler = null;
+
+function isFunction(input) {
+ return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
+}
+
+function set (config) {
+ var prop, i;
+ for (i in config) {
+ prop = config[i];
+ if (isFunction(prop)) {
+ this[i] = prop;
+ } else {
+ this['_' + i] = prop;
+ }
+ }
+ this._config = config;
+ // Lenient ordinal parsing accepts just a number in addition to
+ // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
+ // TODO: Remove "ordinalParse" fallback in next major release.
+ this._dayOfMonthOrdinalParseLenient = new RegExp(
+ (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
+ '|' + (/\d{1,2}/).source);
+}
+
+function mergeConfigs(parentConfig, childConfig) {
+ var res = extend({}, parentConfig), prop;
+ for (prop in childConfig) {
+ if (hasOwnProp(childConfig, prop)) {
+ if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+ res[prop] = {};
+ extend(res[prop], parentConfig[prop]);
+ extend(res[prop], childConfig[prop]);
+ } else if (childConfig[prop] != null) {
+ res[prop] = childConfig[prop];
+ } else {
+ delete res[prop];
+ }
+ }
+ }
+ for (prop in parentConfig) {
+ if (hasOwnProp(parentConfig, prop) &&
+ !hasOwnProp(childConfig, prop) &&
+ isObject(parentConfig[prop])) {
+ // make sure changes to properties don't modify parent config
+ res[prop] = extend({}, res[prop]);
+ }
+ }
+ return res;
+}
+
+function Locale(config) {
+ if (config != null) {
+ this.set(config);
+ }
+}
+
+var keys;
+
+if (Object.keys) {
+ keys = Object.keys;
+} else {
+ keys = function (obj) {
+ var i, res = [];
+ for (i in obj) {
+ if (hasOwnProp(obj, i)) {
+ res.push(i);
+ }
+ }
+ return res;
+ };
+}
+
+var defaultCalendar = {
+ sameDay : '[Today at] LT',
+ nextDay : '[Tomorrow at] LT',
+ nextWeek : 'dddd [at] LT',
+ lastDay : '[Yesterday at] LT',
+ lastWeek : '[Last] dddd [at] LT',
+ sameElse : 'L'
+};
+
+function calendar (key, mom, now) {
+ var output = this._calendar[key] || this._calendar['sameElse'];
+ return isFunction(output) ? output.call(mom, now) : output;
+}
+
+var defaultLongDateFormat = {
+ LTS : 'h:mm:ss A',
+ LT : 'h:mm A',
+ L : 'MM/DD/YYYY',
+ LL : 'MMMM D, YYYY',
+ LLL : 'MMMM D, YYYY h:mm A',
+ LLLL : 'dddd, MMMM D, YYYY h:mm A'
+};
+
+function longDateFormat (key) {
+ var format = this._longDateFormat[key],
+ formatUpper = this._longDateFormat[key.toUpperCase()];
+
+ if (format || !formatUpper) {
+ return format;
+ }
+
+ this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
+ return val.slice(1);
+ });
+
+ return this._longDateFormat[key];
+}
+
+var defaultInvalidDate = 'Invalid date';
+
+function invalidDate () {
+ return this._invalidDate;
+}
+
+var defaultOrdinal = '%d';
+var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
+
+function ordinal (number) {
+ return this._ordinal.replace('%d', number);
+}
+
+var defaultRelativeTime = {
+ future : 'in %s',
+ past : '%s ago',
+ s : 'a few seconds',
+ ss : '%d seconds',
+ m : 'a minute',
+ mm : '%d minutes',
+ h : 'an hour',
+ hh : '%d hours',
+ d : 'a day',
+ dd : '%d days',
+ M : 'a month',
+ MM : '%d months',
+ y : 'a year',
+ yy : '%d years'
+};
+
+function relativeTime (number, withoutSuffix, string, isFuture) {
+ var output = this._relativeTime[string];
+ return (isFunction(output)) ?
+ output(number, withoutSuffix, string, isFuture) :
+ output.replace(/%d/i, number);
+}
+
+function pastFuture (diff, output) {
+ var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+ return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+}
+
+var aliases = {};
+
+function addUnitAlias (unit, shorthand) {
+ var lowerCase = unit.toLowerCase();
+ aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+}
+
+function normalizeUnits(units) {
+ return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
+}
+
+function normalizeObjectUnits(inputObject) {
+ var normalizedInput = {},
+ normalizedProp,
+ prop;
+
+ for (prop in inputObject) {
+ if (hasOwnProp(inputObject, prop)) {
+ normalizedProp = normalizeUnits(prop);
+ if (normalizedProp) {
+ normalizedInput[normalizedProp] = inputObject[prop];
+ }
+ }
+ }
+
+ return normalizedInput;
+}
+
+var priorities = {};
+
+function addUnitPriority(unit, priority) {
+ priorities[unit] = priority;
+}
+
+function getPrioritizedUnits(unitsObj) {
+ var units = [];
+ for (var u in unitsObj) {
+ units.push({unit: u, priority: priorities[u]});
+ }
+ units.sort(function (a, b) {
+ return a.priority - b.priority;
+ });
+ return units;
+}
+
+function zeroFill(number, targetLength, forceSign) {
+ var absNumber = '' + Math.abs(number),
+ zerosToFill = targetLength - absNumber.length,
+ sign = number >= 0;
+ return (sign ? (forceSign ? '+' : '') : '-') +
+ Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
+}
+
+var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
+
+var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
+
+var formatFunctions = {};
+
+var formatTokenFunctions = {};
+
+// token: 'M'
+// padded: ['MM', 2]
+// ordinal: 'Mo'
+// callback: function () { this.month() + 1 }
+function addFormatToken (token, padded, ordinal, callback) {
+ var func = callback;
+ if (typeof callback === 'string') {
+ func = function () {
+ return this[callback]();
+ };
+ }
+ if (token) {
+ formatTokenFunctions[token] = func;
+ }
+ if (padded) {
+ formatTokenFunctions[padded[0]] = function () {
+ return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+ };
+ }
+ if (ordinal) {
+ formatTokenFunctions[ordinal] = function () {
+ return this.localeData().ordinal(func.apply(this, arguments), token);
+ };
+ }
+}
+
+function removeFormattingTokens(input) {
+ if (input.match(/\[[\s\S]/)) {
+ return input.replace(/^\[|\]$/g, '');
+ }
+ return input.replace(/\\/g, '');
+}
+
+function makeFormatFunction(format) {
+ var array = format.match(formattingTokens), i, length;
+
+ for (i = 0, length = array.length; i < length; i++) {
+ if (formatTokenFunctions[array[i]]) {
+ array[i] = formatTokenFunctions[array[i]];
+ } else {
+ array[i] = removeFormattingTokens(array[i]);
+ }
+ }
+
+ return function (mom) {
+ var output = '', i;
+ for (i = 0; i < length; i++) {
+ output += isFunction(array[i]) ? array[i].call(mom, format) : array[i];
+ }
+ return output;
+ };
+}
+
+// format date using native date object
+function formatMoment(m, format) {
+ if (!m.isValid()) {
+ return m.localeData().invalidDate();
+ }
+
+ format = expandFormat(format, m.localeData());
+ formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
+
+ return formatFunctions[format](m);
+}
+
+function expandFormat(format, locale) {
+ var i = 5;
+
+ function replaceLongDateFormatTokens(input) {
+ return locale.longDateFormat(input) || input;
+ }
+
+ localFormattingTokens.lastIndex = 0;
+ while (i >= 0 && localFormattingTokens.test(format)) {
+ format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+ localFormattingTokens.lastIndex = 0;
+ i -= 1;
+ }
+
+ return format;
+}
+
+var match1 = /\d/; // 0 - 9
+var match2 = /\d\d/; // 00 - 99
+var match3 = /\d{3}/; // 000 - 999
+var match4 = /\d{4}/; // 0000 - 9999
+var match6 = /[+-]?\d{6}/; // -999999 - 999999
+var match1to2 = /\d\d?/; // 0 - 99
+var match3to4 = /\d\d\d\d?/; // 999 - 9999
+var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999
+var match1to3 = /\d{1,3}/; // 0 - 999
+var match1to4 = /\d{1,4}/; // 0 - 9999
+var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
+
+var matchUnsigned = /\d+/; // 0 - inf
+var matchSigned = /[+-]?\d+/; // -inf - inf
+
+var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
+var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+
+var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
+
+// any word (or two) characters or numbers including two/three word month in arabic.
+// includes scottish gaelic two word and hyphenated months
+var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
+
+
+var regexes = {};
+
+function addRegexToken (token, regex, strictRegex) {
+ regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
+ return (isStrict && strictRegex) ? strictRegex : regex;
+ };
+}
+
+function getParseRegexForToken (token, config) {
+ if (!hasOwnProp(regexes, token)) {
+ return new RegExp(unescapeFormat(token));
+ }
+
+ return regexes[token](config._strict, config._locale);
+}
+
+// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+function unescapeFormat(s) {
+ return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+ return p1 || p2 || p3 || p4;
+ }));
+}
+
+function regexEscape(s) {
+ return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
+
+var tokens = {};
+
+function addParseToken (token, callback) {
+ var i, func = callback;
+ if (typeof token === 'string') {
+ token = [token];
+ }
+ if (isNumber(callback)) {
+ func = function (input, array) {
+ array[callback] = toInt(input);
+ };
+ }
+ for (i = 0; i < token.length; i++) {
+ tokens[token[i]] = func;
+ }
+}
+
+function addWeekParseToken (token, callback) {
+ addParseToken(token, function (input, array, config, token) {
+ config._w = config._w || {};
+ callback(input, config._w, config, token);
+ });
+}
+
+function addTimeToArrayFromToken(token, input, config) {
+ if (input != null && hasOwnProp(tokens, token)) {
+ tokens[token](input, config._a, config, token);
+ }
+}
+
+var YEAR = 0;
+var MONTH = 1;
+var DATE = 2;
+var HOUR = 3;
+var MINUTE = 4;
+var SECOND = 5;
+var MILLISECOND = 6;
+var WEEK = 7;
+var WEEKDAY = 8;
+
+// FORMATTING
+
+addFormatToken('Y', 0, 0, function () {
+ var y = this.year();
+ return y <= 9999 ? '' + y : '+' + y;
+});
+
+addFormatToken(0, ['YY', 2], 0, function () {
+ return this.year() % 100;
+});
+
+addFormatToken(0, ['YYYY', 4], 0, 'year');
+addFormatToken(0, ['YYYYY', 5], 0, 'year');
+addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
+
+// ALIASES
+
+addUnitAlias('year', 'y');
+
+// PRIORITIES
+
+addUnitPriority('year', 1);
+
+// PARSING
+
+addRegexToken('Y', matchSigned);
+addRegexToken('YY', match1to2, match2);
+addRegexToken('YYYY', match1to4, match4);
+addRegexToken('YYYYY', match1to6, match6);
+addRegexToken('YYYYYY', match1to6, match6);
+
+addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+addParseToken('YYYY', function (input, array) {
+ array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
+});
+addParseToken('YY', function (input, array) {
+ array[YEAR] = hooks.parseTwoDigitYear(input);
+});
+addParseToken('Y', function (input, array) {
+ array[YEAR] = parseInt(input, 10);
+});
+
+// HELPERS
+
+function daysInYear(year) {
+ return isLeapYear(year) ? 366 : 365;
+}
+
+function isLeapYear(year) {
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+}
+
+// HOOKS
+
+hooks.parseTwoDigitYear = function (input) {
+ return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+};
+
+// MOMENTS
+
+var getSetYear = makeGetSet('FullYear', true);
+
+function getIsLeapYear () {
+ return isLeapYear(this.year());
+}
+
+function makeGetSet (unit, keepTime) {
+ return function (value) {
+ if (value != null) {
+ set$1(this, unit, value);
+ hooks.updateOffset(this, keepTime);
+ return this;
+ } else {
+ return get(this, unit);
+ }
+ };
+}
+
+function get (mom, unit) {
+ return mom.isValid() ?
+ mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
+}
+
+function set$1 (mom, unit, value) {
+ if (mom.isValid() && !isNaN(value)) {
+ if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
+ mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
+ }
+ else {
+ mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+ }
+ }
+}
+
+// MOMENTS
+
+function stringGet (units) {
+ units = normalizeUnits(units);
+ if (isFunction(this[units])) {
+ return this[units]();
+ }
+ return this;
+}
+
+
+function stringSet (units, value) {
+ if (typeof units === 'object') {
+ units = normalizeObjectUnits(units);
+ var prioritized = getPrioritizedUnits(units);
+ for (var i = 0; i < prioritized.length; i++) {
+ this[prioritized[i].unit](units[prioritized[i].unit]);
+ }
+ } else {
+ units = normalizeUnits(units);
+ if (isFunction(this[units])) {
+ return this[units](value);
+ }
+ }
+ return this;
+}
+
+function mod(n, x) {
+ return ((n % x) + x) % x;
+}
+
+var indexOf;
+
+if (Array.prototype.indexOf) {
+ indexOf = Array.prototype.indexOf;
+} else {
+ indexOf = function (o) {
+ // I know
+ var i;
+ for (i = 0; i < this.length; ++i) {
+ if (this[i] === o) {
+ return i;
+ }
+ }
+ return -1;
+ };
+}
+
+function daysInMonth(year, month) {
+ if (isNaN(year) || isNaN(month)) {
+ return NaN;
+ }
+ var modMonth = mod(month, 12);
+ year += (month - modMonth) / 12;
+ return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
+}
+
+// FORMATTING
+
+addFormatToken('M', ['MM', 2], 'Mo', function () {
+ return this.month() + 1;
+});
+
+addFormatToken('MMM', 0, 0, function (format) {
+ return this.localeData().monthsShort(this, format);
+});
+
+addFormatToken('MMMM', 0, 0, function (format) {
+ return this.localeData().months(this, format);
+});
+
+// ALIASES
+
+addUnitAlias('month', 'M');
+
+// PRIORITY
+
+addUnitPriority('month', 8);
+
+// PARSING
+
+addRegexToken('M', match1to2);
+addRegexToken('MM', match1to2, match2);
+addRegexToken('MMM', function (isStrict, locale) {
+ return locale.monthsShortRegex(isStrict);
+});
+addRegexToken('MMMM', function (isStrict, locale) {
+ return locale.monthsRegex(isStrict);
+});
+
+addParseToken(['M', 'MM'], function (input, array) {
+ array[MONTH] = toInt(input) - 1;
+});
+
+addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+ var month = config._locale.monthsParse(input, token, config._strict);
+ // if we didn't find a month name, mark the date as invalid.
+ if (month != null) {
+ array[MONTH] = month;
+ } else {
+ getParsingFlags(config).invalidMonth = input;
+ }
+});
+
+// LOCALES
+
+var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
+var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
+function localeMonths (m, format) {
+ if (!m) {
+ return isArray(this._months) ? this._months :
+ this._months['standalone'];
+ }
+ return isArray(this._months) ? this._months[m.month()] :
+ this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
+}
+
+var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
+function localeMonthsShort (m, format) {
+ if (!m) {
+ return isArray(this._monthsShort) ? this._monthsShort :
+ this._monthsShort['standalone'];
+ }
+ return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
+ this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
+}
+
+function handleStrictParse(monthName, format, strict) {
+ var i, ii, mom, llc = monthName.toLocaleLowerCase();
+ if (!this._monthsParse) {
+ // this is not used
+ this._monthsParse = [];
+ this._longMonthsParse = [];
+ this._shortMonthsParse = [];
+ for (i = 0; i < 12; ++i) {
+ mom = createUTC([2000, i]);
+ this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
+ this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+ }
+ }
+
+ if (strict) {
+ if (format === 'MMM') {
+ ii = indexOf.call(this._shortMonthsParse, llc);
+ return ii !== -1 ? ii : null;
+ } else {
+ ii = indexOf.call(this._longMonthsParse, llc);
+ return ii !== -1 ? ii : null;
+ }
+ } else {
+ if (format === 'MMM') {
+ ii = indexOf.call(this._shortMonthsParse, llc);
+ if (ii !== -1) {
+ return ii;
+ }
+ ii = indexOf.call(this._longMonthsParse, llc);
+ return ii !== -1 ? ii : null;
+ } else {
+ ii = indexOf.call(this._longMonthsParse, llc);
+ if (ii !== -1) {
+ return ii;
+ }
+ ii = indexOf.call(this._shortMonthsParse, llc);
+ return ii !== -1 ? ii : null;
+ }
+ }
+}
+
+function localeMonthsParse (monthName, format, strict) {
+ var i, mom, regex;
+
+ if (this._monthsParseExact) {
+ return handleStrictParse.call(this, monthName, format, strict);
+ }
+
+ if (!this._monthsParse) {
+ this._monthsParse = [];
+ this._longMonthsParse = [];
+ this._shortMonthsParse = [];
+ }
+
+ // TODO: add sorting
+ // Sorting makes sure if one month (or abbr) is a prefix of another
+ // see sorting in computeMonthsParse
+ for (i = 0; i < 12; i++) {
+ // make the regex if we don't have it already
+ mom = createUTC([2000, i]);
+ if (strict && !this._longMonthsParse[i]) {
+ this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
+ this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
+ }
+ if (!strict && !this._monthsParse[i]) {
+ regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+ this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
+ }
+ // test the regex
+ if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
+ return i;
+ } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
+ return i;
+ } else if (!strict && this._monthsParse[i].test(monthName)) {
+ return i;
+ }
+ }
+}
+
+// MOMENTS
+
+function setMonth (mom, value) {
+ var dayOfMonth;
+
+ if (!mom.isValid()) {
+ // No op
+ return mom;
+ }
+
+ if (typeof value === 'string') {
+ if (/^\d+$/.test(value)) {
+ value = toInt(value);
+ } else {
+ value = mom.localeData().monthsParse(value);
+ // TODO: Another silent failure?
+ if (!isNumber(value)) {
+ return mom;
+ }
+ }
+ }
+
+ dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+ mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+ return mom;
+}
+
+function getSetMonth (value) {
+ if (value != null) {
+ setMonth(this, value);
+ hooks.updateOffset(this, true);
+ return this;
+ } else {
+ return get(this, 'Month');
+ }
+}
+
+function getDaysInMonth () {
+ return daysInMonth(this.year(), this.month());
+}
+
+var defaultMonthsShortRegex = matchWord;
+function monthsShortRegex (isStrict) {
+ if (this._monthsParseExact) {
+ if (!hasOwnProp(this, '_monthsRegex')) {
+ computeMonthsParse.call(this);
+ }
+ if (isStrict) {
+ return this._monthsShortStrictRegex;
+ } else {
+ return this._monthsShortRegex;
+ }
+ } else {
+ if (!hasOwnProp(this, '_monthsShortRegex')) {
+ this._monthsShortRegex = defaultMonthsShortRegex;
+ }
+ return this._monthsShortStrictRegex && isStrict ?
+ this._monthsShortStrictRegex : this._monthsShortRegex;
+ }
+}
+
+var defaultMonthsRegex = matchWord;
+function monthsRegex (isStrict) {
+ if (this._monthsParseExact) {
+ if (!hasOwnProp(this, '_monthsRegex')) {
+ computeMonthsParse.call(this);
+ }
+ if (isStrict) {
+ return this._monthsStrictRegex;
+ } else {
+ return this._monthsRegex;
+ }
+ } else {
+ if (!hasOwnProp(this, '_monthsRegex')) {
+ this._monthsRegex = defaultMonthsRegex;
+ }
+ return this._monthsStrictRegex && isStrict ?
+ this._monthsStrictRegex : this._monthsRegex;
+ }
+}
+
+function computeMonthsParse () {
+ function cmpLenRev(a, b) {
+ return b.length - a.length;
+ }
+
+ var shortPieces = [], longPieces = [], mixedPieces = [],
+ i, mom;
+ for (i = 0; i < 12; i++) {
+ // make the regex if we don't have it already
+ mom = createUTC([2000, i]);
+ shortPieces.push(this.monthsShort(mom, ''));
+ longPieces.push(this.months(mom, ''));
+ mixedPieces.push(this.months(mom, ''));
+ mixedPieces.push(this.monthsShort(mom, ''));
+ }
+ // Sorting makes sure if one month (or abbr) is a prefix of another it
+ // will match the longer piece.
+ shortPieces.sort(cmpLenRev);
+ longPieces.sort(cmpLenRev);
+ mixedPieces.sort(cmpLenRev);
+ for (i = 0; i < 12; i++) {
+ shortPieces[i] = regexEscape(shortPieces[i]);
+ longPieces[i] = regexEscape(longPieces[i]);
+ }
+ for (i = 0; i < 24; i++) {
+ mixedPieces[i] = regexEscape(mixedPieces[i]);
+ }
+
+ this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+ this._monthsShortRegex = this._monthsRegex;
+ this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+ this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+}
+
+function createDate (y, m, d, h, M, s, ms) {
+ // can't just apply() to create a date:
+ // https://stackoverflow.com/q/181348
+ var date = new Date(y, m, d, h, M, s, ms);
+
+ // the date constructor remaps years 0-99 to 1900-1999
+ if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
+ date.setFullYear(y);
+ }
+ return date;
+}
+
+function createUTCDate (y) {
+ var date = new Date(Date.UTC.apply(null, arguments));
+
+ // the Date.UTC function remaps years 0-99 to 1900-1999
+ if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
+ date.setUTCFullYear(y);
+ }
+ return date;
+}
+
+// start-of-first-week - start-of-year
+function firstWeekOffset(year, dow, doy) {
+ var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+ fwd = 7 + dow - doy,
+ // first-week day local weekday -- which local weekday is fwd
+ fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+
+ return -fwdlw + fwd - 1;
+}
+
+// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+ var localWeekday = (7 + weekday - dow) % 7,
+ weekOffset = firstWeekOffset(year, dow, doy),
+ dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+ resYear, resDayOfYear;
+
+ if (dayOfYear <= 0) {
+ resYear = year - 1;
+ resDayOfYear = daysInYear(resYear) + dayOfYear;
+ } else if (dayOfYear > daysInYear(year)) {
+ resYear = year + 1;
+ resDayOfYear = dayOfYear - daysInYear(year);
+ } else {
+ resYear = year;
+ resDayOfYear = dayOfYear;
+ }
+
+ return {
+ year: resYear,
+ dayOfYear: resDayOfYear
+ };
+}
+
+function weekOfYear(mom, dow, doy) {
+ var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+ week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+ resWeek, resYear;
+
+ if (week < 1) {
+ resYear = mom.year() - 1;
+ resWeek = week + weeksInYear(resYear, dow, doy);
+ } else if (week > weeksInYear(mom.year(), dow, doy)) {
+ resWeek = week - weeksInYear(mom.year(), dow, doy);
+ resYear = mom.year() + 1;
+ } else {
+ resYear = mom.year();
+ resWeek = week;
+ }
+
+ return {
+ week: resWeek,
+ year: resYear
+ };
+}
+
+function weeksInYear(year, dow, doy) {
+ var weekOffset = firstWeekOffset(year, dow, doy),
+ weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+ return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+}
+
+// FORMATTING
+
+addFormatToken('w', ['ww', 2], 'wo', 'week');
+addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+// ALIASES
+
+addUnitAlias('week', 'w');
+addUnitAlias('isoWeek', 'W');
+
+// PRIORITIES
+
+addUnitPriority('week', 5);
+addUnitPriority('isoWeek', 5);
+
+// PARSING
+
+addRegexToken('w', match1to2);
+addRegexToken('ww', match1to2, match2);
+addRegexToken('W', match1to2);
+addRegexToken('WW', match1to2, match2);
+
+addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
+ week[token.substr(0, 1)] = toInt(input);
+});
+
+// HELPERS
+
+// LOCALES
+
+function localeWeek (mom) {
+ return weekOfYear(mom, this._week.dow, this._week.doy).week;
+}
+
+var defaultLocaleWeek = {
+ dow : 0, // Sunday is the first day of the week.
+ doy : 6 // The week that contains Jan 1st is the first week of the year.
+};
+
+function localeFirstDayOfWeek () {
+ return this._week.dow;
+}
+
+function localeFirstDayOfYear () {
+ return this._week.doy;
+}
+
+// MOMENTS
+
+function getSetWeek (input) {
+ var week = this.localeData().week(this);
+ return input == null ? week : this.add((input - week) * 7, 'd');
+}
+
+function getSetISOWeek (input) {
+ var week = weekOfYear(this, 1, 4).week;
+ return input == null ? week : this.add((input - week) * 7, 'd');
+}
+
+// FORMATTING
+
+addFormatToken('d', 0, 'do', 'day');
+
+addFormatToken('dd', 0, 0, function (format) {
+ return this.localeData().weekdaysMin(this, format);
+});
+
+addFormatToken('ddd', 0, 0, function (format) {
+ return this.localeData().weekdaysShort(this, format);
+});
+
+addFormatToken('dddd', 0, 0, function (format) {
+ return this.localeData().weekdays(this, format);
+});
+
+addFormatToken('e', 0, 0, 'weekday');
+addFormatToken('E', 0, 0, 'isoWeekday');
+
+// ALIASES
+
+addUnitAlias('day', 'd');
+addUnitAlias('weekday', 'e');
+addUnitAlias('isoWeekday', 'E');
+
+// PRIORITY
+addUnitPriority('day', 11);
+addUnitPriority('weekday', 11);
+addUnitPriority('isoWeekday', 11);
+
+// PARSING
+
+addRegexToken('d', match1to2);
+addRegexToken('e', match1to2);
+addRegexToken('E', match1to2);
+addRegexToken('dd', function (isStrict, locale) {
+ return locale.weekdaysMinRegex(isStrict);
+});
+addRegexToken('ddd', function (isStrict, locale) {
+ return locale.weekdaysShortRegex(isStrict);
+});
+addRegexToken('dddd', function (isStrict, locale) {
+ return locale.weekdaysRegex(isStrict);
+});
+
+addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+ var weekday = config._locale.weekdaysParse(input, token, config._strict);
+ // if we didn't get a weekday name, mark the date as invalid
+ if (weekday != null) {
+ week.d = weekday;
+ } else {
+ getParsingFlags(config).invalidWeekday = input;
+ }
+});
+
+addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+ week[token] = toInt(input);
+});
+
+// HELPERS
+
+function parseWeekday(input, locale) {
+ if (typeof input !== 'string') {
+ return input;
+ }
+
+ if (!isNaN(input)) {
+ return parseInt(input, 10);
+ }
+
+ input = locale.weekdaysParse(input);
+ if (typeof input === 'number') {
+ return input;
+ }
+
+ return null;
+}
+
+function parseIsoWeekday(input, locale) {
+ if (typeof input === 'string') {
+ return locale.weekdaysParse(input) % 7 || 7;
+ }
+ return isNaN(input) ? null : input;
+}
+
+// LOCALES
+
+var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
+function localeWeekdays (m, format) {
+ if (!m) {
+ return isArray(this._weekdays) ? this._weekdays :
+ this._weekdays['standalone'];
+ }
+ return isArray(this._weekdays) ? this._weekdays[m.day()] :
+ this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
+}
+
+var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
+function localeWeekdaysShort (m) {
+ return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
+}
+
+var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
+function localeWeekdaysMin (m) {
+ return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
+}
+
+function handleStrictParse$1(weekdayName, format, strict) {
+ var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
+ if (!this._weekdaysParse) {
+ this._weekdaysParse = [];
+ this._shortWeekdaysParse = [];
+ this._minWeekdaysParse = [];
+
+ for (i = 0; i < 7; ++i) {
+ mom = createUTC([2000, 1]).day(i);
+ this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
+ this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
+ this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+ }
+ }
+
+ if (strict) {
+ if (format === 'dddd') {
+ ii = indexOf.call(this._weekdaysParse, llc);
+ return ii !== -1 ? ii : null;
+ } else if (format === 'ddd') {
+ ii = indexOf.call(this._shortWeekdaysParse, llc);
+ return ii !== -1 ? ii : null;
+ } else {
+ ii = indexOf.call(this._minWeekdaysParse, llc);
+ return ii !== -1 ? ii : null;
+ }
+ } else {
+ if (format === 'dddd') {
+ ii = indexOf.call(this._weekdaysParse, llc);
+ if (ii !== -1) {
+ return ii;
+ }
+ ii = indexOf.call(this._shortWeekdaysParse, llc);
+ if (ii !== -1) {
+ return ii;
+ }
+ ii = indexOf.call(this._minWeekdaysParse, llc);
+ return ii !== -1 ? ii : null;
+ } else if (format === 'ddd') {
+ ii = indexOf.call(this._shortWeekdaysParse, llc);
+ if (ii !== -1) {
+ return ii;
+ }
+ ii = indexOf.call(this._weekdaysParse, llc);
+ if (ii !== -1) {
+ return ii;
+ }
+ ii = indexOf.call(this._minWeekdaysParse, llc);
+ return ii !== -1 ? ii : null;
+ } else {
+ ii = indexOf.call(this._minWeekdaysParse, llc);
+ if (ii !== -1) {
+ return ii;
+ }
+ ii = indexOf.call(this._weekdaysParse, llc);
+ if (ii !== -1) {
+ return ii;
+ }
+ ii = indexOf.call(this._shortWeekdaysParse, llc);
+ return ii !== -1 ? ii : null;
+ }
+ }
+}
+
+function localeWeekdaysParse (weekdayName, format, strict) {
+ var i, mom, regex;
+
+ if (this._weekdaysParseExact) {
+ return handleStrictParse$1.call(this, weekdayName, format, strict);
+ }
+
+ if (!this._weekdaysParse) {
+ this._weekdaysParse = [];
+ this._minWeekdaysParse = [];
+ this._shortWeekdaysParse = [];
+ this._fullWeekdaysParse = [];
+ }
+
+ for (i = 0; i < 7; i++) {
+ // make the regex if we don't have it already
+
+ mom = createUTC([2000, 1]).day(i);
+ if (strict && !this._fullWeekdaysParse[i]) {
+ this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
+ this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
+ this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
+ }
+ if (!this._weekdaysParse[i]) {
+ regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
+ this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+ }
+ // test the regex
+ if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
+ return i;
+ } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
+ return i;
+ } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
+ return i;
+ } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+ return i;
+ }
+ }
+}
+
+// MOMENTS
+
+function getSetDayOfWeek (input) {
+ if (!this.isValid()) {
+ return input != null ? this : NaN;
+ }
+ var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+ if (input != null) {
+ input = parseWeekday(input, this.localeData());
+ return this.add(input - day, 'd');
+ } else {
+ return day;
+ }
+}
+
+function getSetLocaleDayOfWeek (input) {
+ if (!this.isValid()) {
+ return input != null ? this : NaN;
+ }
+ var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+ return input == null ? weekday : this.add(input - weekday, 'd');
+}
+
+function getSetISODayOfWeek (input) {
+ if (!this.isValid()) {
+ return input != null ? this : NaN;
+ }
+
+ // behaves the same as moment#day except
+ // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+ // as a setter, sunday should belong to the previous week.
+
+ if (input != null) {
+ var weekday = parseIsoWeekday(input, this.localeData());
+ return this.day(this.day() % 7 ? weekday : weekday - 7);
+ } else {
+ return this.day() || 7;
+ }
+}
+
+var defaultWeekdaysRegex = matchWord;
+function weekdaysRegex (isStrict) {
+ if (this._weekdaysParseExact) {
+ if (!hasOwnProp(this, '_weekdaysRegex')) {
+ computeWeekdaysParse.call(this);
+ }
+ if (isStrict) {
+ return this._weekdaysStrictRegex;
+ } else {
+ return this._weekdaysRegex;
+ }
+ } else {
+ if (!hasOwnProp(this, '_weekdaysRegex')) {
+ this._weekdaysRegex = defaultWeekdaysRegex;
+ }
+ return this._weekdaysStrictRegex && isStrict ?
+ this._weekdaysStrictRegex : this._weekdaysRegex;
+ }
+}
+
+var defaultWeekdaysShortRegex = matchWord;
+function weekdaysShortRegex (isStrict) {
+ if (this._weekdaysParseExact) {
+ if (!hasOwnProp(this, '_weekdaysRegex')) {
+ computeWeekdaysParse.call(this);
+ }
+ if (isStrict) {
+ return this._weekdaysShortStrictRegex;
+ } else {
+ return this._weekdaysShortRegex;
+ }
+ } else {
+ if (!hasOwnProp(this, '_weekdaysShortRegex')) {
+ this._weekdaysShortRegex = defaultWeekdaysShortRegex;
+ }
+ return this._weekdaysShortStrictRegex && isStrict ?
+ this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
+ }
+}
+
+var defaultWeekdaysMinRegex = matchWord;
+function weekdaysMinRegex (isStrict) {
+ if (this._weekdaysParseExact) {
+ if (!hasOwnProp(this, '_weekdaysRegex')) {
+ computeWeekdaysParse.call(this);
+ }
+ if (isStrict) {
+ return this._weekdaysMinStrictRegex;
+ } else {
+ return this._weekdaysMinRegex;
+ }
+ } else {
+ if (!hasOwnProp(this, '_weekdaysMinRegex')) {
+ this._weekdaysMinRegex = defaultWeekdaysMinRegex;
+ }
+ return this._weekdaysMinStrictRegex && isStrict ?
+ this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
+ }
+}
+
+
+function computeWeekdaysParse () {
+ function cmpLenRev(a, b) {
+ return b.length - a.length;
+ }
+
+ var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
+ i, mom, minp, shortp, longp;
+ for (i = 0; i < 7; i++) {
+ // make the regex if we don't have it already
+ mom = createUTC([2000, 1]).day(i);
+ minp = this.weekdaysMin(mom, '');
+ shortp = this.weekdaysShort(mom, '');
+ longp = this.weekdays(mom, '');
+ minPieces.push(minp);
+ shortPieces.push(shortp);
+ longPieces.push(longp);
+ mixedPieces.push(minp);
+ mixedPieces.push(shortp);
+ mixedPieces.push(longp);
+ }
+ // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+ // will match the longer piece.
+ minPieces.sort(cmpLenRev);
+ shortPieces.sort(cmpLenRev);
+ longPieces.sort(cmpLenRev);
+ mixedPieces.sort(cmpLenRev);
+ for (i = 0; i < 7; i++) {
+ shortPieces[i] = regexEscape(shortPieces[i]);
+ longPieces[i] = regexEscape(longPieces[i]);
+ mixedPieces[i] = regexEscape(mixedPieces[i]);
+ }
+
+ this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+ this._weekdaysShortRegex = this._weekdaysRegex;
+ this._weekdaysMinRegex = this._weekdaysRegex;
+
+ this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+ this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+ this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
+}
+
+// FORMATTING
+
+function hFormat() {
+ return this.hours() % 12 || 12;
+}
+
+function kFormat() {
+ return this.hours() || 24;
+}
+
+addFormatToken('H', ['HH', 2], 0, 'hour');
+addFormatToken('h', ['hh', 2], 0, hFormat);
+addFormatToken('k', ['kk', 2], 0, kFormat);
+
+addFormatToken('hmm', 0, 0, function () {
+ return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+});
+
+addFormatToken('hmmss', 0, 0, function () {
+ return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
+ zeroFill(this.seconds(), 2);
+});
+
+addFormatToken('Hmm', 0, 0, function () {
+ return '' + this.hours() + zeroFill(this.minutes(), 2);
+});
+
+addFormatToken('Hmmss', 0, 0, function () {
+ return '' + this.hours() + zeroFill(this.minutes(), 2) +
+ zeroFill(this.seconds(), 2);
+});
+
+function meridiem (token, lowercase) {
+ addFormatToken(token, 0, 0, function () {
+ return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
+ });
+}
+
+meridiem('a', true);
+meridiem('A', false);
+
+// ALIASES
+
+addUnitAlias('hour', 'h');
+
+// PRIORITY
+addUnitPriority('hour', 13);
+
+// PARSING
+
+function matchMeridiem (isStrict, locale) {
+ return locale._meridiemParse;
+}
+
+addRegexToken('a', matchMeridiem);
+addRegexToken('A', matchMeridiem);
+addRegexToken('H', match1to2);
+addRegexToken('h', match1to2);
+addRegexToken('k', match1to2);
+addRegexToken('HH', match1to2, match2);
+addRegexToken('hh', match1to2, match2);
+addRegexToken('kk', match1to2, match2);
+
+addRegexToken('hmm', match3to4);
+addRegexToken('hmmss', match5to6);
+addRegexToken('Hmm', match3to4);
+addRegexToken('Hmmss', match5to6);
+
+addParseToken(['H', 'HH'], HOUR);
+addParseToken(['k', 'kk'], function (input, array, config) {
+ var kInput = toInt(input);
+ array[HOUR] = kInput === 24 ? 0 : kInput;
+});
+addParseToken(['a', 'A'], function (input, array, config) {
+ config._isPm = config._locale.isPM(input);
+ config._meridiem = input;
+});
+addParseToken(['h', 'hh'], function (input, array, config) {
+ array[HOUR] = toInt(input);
+ getParsingFlags(config).bigHour = true;
+});
+addParseToken('hmm', function (input, array, config) {
+ var pos = input.length - 2;
+ array[HOUR] = toInt(input.substr(0, pos));
+ array[MINUTE] = toInt(input.substr(pos));
+ getParsingFlags(config).bigHour = true;
+});
+addParseToken('hmmss', function (input, array, config) {
+ var pos1 = input.length - 4;
+ var pos2 = input.length - 2;
+ array[HOUR] = toInt(input.substr(0, pos1));
+ array[MINUTE] = toInt(input.substr(pos1, 2));
+ array[SECOND] = toInt(input.substr(pos2));
+ getParsingFlags(config).bigHour = true;
+});
+addParseToken('Hmm', function (input, array, config) {
+ var pos = input.length - 2;
+ array[HOUR] = toInt(input.substr(0, pos));
+ array[MINUTE] = toInt(input.substr(pos));
+});
+addParseToken('Hmmss', function (input, array, config) {
+ var pos1 = input.length - 4;
+ var pos2 = input.length - 2;
+ array[HOUR] = toInt(input.substr(0, pos1));
+ array[MINUTE] = toInt(input.substr(pos1, 2));
+ array[SECOND] = toInt(input.substr(pos2));
+});
+
+// LOCALES
+
+function localeIsPM (input) {
+ // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+ // Using charAt should be more compatible.
+ return ((input + '').toLowerCase().charAt(0) === 'p');
+}
+
+var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
+function localeMeridiem (hours, minutes, isLower) {
+ if (hours > 11) {
+ return isLower ? 'pm' : 'PM';
+ } else {
+ return isLower ? 'am' : 'AM';
+ }
+}
+
+
+// MOMENTS
+
+// Setting the hour should keep the time, because the user explicitly
+// specified which hour he wants. So trying to maintain the same hour (in
+// a new timezone) makes sense. Adding/subtracting hours does not follow
+// this rule.
+var getSetHour = makeGetSet('Hours', true);
+
+// months
+// week
+// weekdays
+// meridiem
+var baseConfig = {
+ calendar: defaultCalendar,
+ longDateFormat: defaultLongDateFormat,
+ invalidDate: defaultInvalidDate,
+ ordinal: defaultOrdinal,
+ dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
+ relativeTime: defaultRelativeTime,
+
+ months: defaultLocaleMonths,
+ monthsShort: defaultLocaleMonthsShort,
+
+ week: defaultLocaleWeek,
+
+ weekdays: defaultLocaleWeekdays,
+ weekdaysMin: defaultLocaleWeekdaysMin,
+ weekdaysShort: defaultLocaleWeekdaysShort,
+
+ meridiemParse: defaultLocaleMeridiemParse
+};
+
+// internal storage for locale config files
+var locales = {};
+var localeFamilies = {};
+var globalLocale;
+
+function normalizeLocale(key) {
+ return key ? key.toLowerCase().replace('_', '-') : key;
+}
+
+// pick the locale from the array
+// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+function chooseLocale(names) {
+ var i = 0, j, next, locale, split;
+
+ while (i < names.length) {
+ split = normalizeLocale(names[i]).split('-');
+ j = split.length;
+ next = normalizeLocale(names[i + 1]);
+ next = next ? next.split('-') : null;
+ while (j > 0) {
+ locale = loadLocale(split.slice(0, j).join('-'));
+ if (locale) {
+ return locale;
+ }
+ if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+ //the next array item is better than a shallower substring of this one
+ break;
+ }
+ j--;
+ }
+ i++;
+ }
+ return null;
+}
+
+function loadLocale(name) {
+ var oldLocale = null;
+ // TODO: Find a better way to register and load all the locales in Node
+ if (!locales[name] && (typeof module !== 'undefined') &&
+ module && module.exports) {
+ try {
+ oldLocale = globalLocale._abbr;
+ var aliasedRequire = require;
+ aliasedRequire('./locale/' + name);
+ getSetGlobalLocale(oldLocale);
+ } catch (e) {}
+ }
+ return locales[name];
+}
+
+// This function will load locale and then set the global locale. If
+// no arguments are passed in, it will simply return the current global
+// locale key.
+function getSetGlobalLocale (key, values) {
+ var data;
+ if (key) {
+ if (isUndefined(values)) {
+ data = getLocale(key);
+ }
+ else {
+ data = defineLocale(key, values);
+ }
+
+ if (data) {
+ // moment.duration._locale = moment._locale = data;
+ globalLocale = data;
+ }
+ }
+
+ return globalLocale._abbr;
+}
+
+function defineLocale (name, config) {
+ if (config !== null) {
+ var parentConfig = baseConfig;
+ config.abbr = name;
+ if (locales[name] != null) {
+ deprecateSimple('defineLocaleOverride',
+ 'use moment.updateLocale(localeName, config) to change ' +
+ 'an existing locale. moment.defineLocale(localeName, ' +
+ 'config) should only be used for creating a new locale ' +
+ 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
+ parentConfig = locales[name]._config;
+ } else if (config.parentLocale != null) {
+ if (locales[config.parentLocale] != null) {
+ parentConfig = locales[config.parentLocale]._config;
+ } else {
+ if (!localeFamilies[config.parentLocale]) {
+ localeFamilies[config.parentLocale] = [];
+ }
+ localeFamilies[config.parentLocale].push({
+ name: name,
+ config: config
+ });
+ return null;
+ }
+ }
+ locales[name] = new Locale(mergeConfigs(parentConfig, config));
+
+ if (localeFamilies[name]) {
+ localeFamilies[name].forEach(function (x) {
+ defineLocale(x.name, x.config);
+ });
+ }
+
+ // backwards compat for now: also set the locale
+ // make sure we set the locale AFTER all child locales have been
+ // created, so we won't end up with the child locale set.
+ getSetGlobalLocale(name);
+
+
+ return locales[name];
+ } else {
+ // useful for testing
+ delete locales[name];
+ return null;
+ }
+}
+
+function updateLocale(name, config) {
+ if (config != null) {
+ var locale, tmpLocale, parentConfig = baseConfig;
+ // MERGE
+ tmpLocale = loadLocale(name);
+ if (tmpLocale != null) {
+ parentConfig = tmpLocale._config;
+ }
+ config = mergeConfigs(parentConfig, config);
+ locale = new Locale(config);
+ locale.parentLocale = locales[name];
+ locales[name] = locale;
+
+ // backwards compat for now: also set the locale
+ getSetGlobalLocale(name);
+ } else {
+ // pass null for config to unupdate, useful for tests
+ if (locales[name] != null) {
+ if (locales[name].parentLocale != null) {
+ locales[name] = locales[name].parentLocale;
+ } else if (locales[name] != null) {
+ delete locales[name];
+ }
+ }
+ }
+ return locales[name];
+}
+
+// returns locale data
+function getLocale (key) {
+ var locale;
+
+ if (key && key._locale && key._locale._abbr) {
+ key = key._locale._abbr;
+ }
+
+ if (!key) {
+ return globalLocale;
+ }
+
+ if (!isArray(key)) {
+ //short-circuit everything else
+ locale = loadLocale(key);
+ if (locale) {
+ return locale;
+ }
+ key = [key];
+ }
+
+ return chooseLocale(key);
+}
+
+function listLocales() {
+ return keys(locales);
+}
+
+function checkOverflow (m) {
+ var overflow;
+ var a = m._a;
+
+ if (a && getParsingFlags(m).overflow === -2) {
+ overflow =
+ a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
+ a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
+ a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
+ a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
+ a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
+ a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
+ -1;
+
+ if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+ overflow = DATE;
+ }
+ if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+ overflow = WEEK;
+ }
+ if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+ overflow = WEEKDAY;
+ }
+
+ getParsingFlags(m).overflow = overflow;
+ }
+
+ return m;
+}
+
+// Pick the first defined of two or three arguments.
+function defaults(a, b, c) {
+ if (a != null) {
+ return a;
+ }
+ if (b != null) {
+ return b;
+ }
+ return c;
+}
+
+function currentDateArray(config) {
+ // hooks is actually the exported moment object
+ var nowValue = new Date(hooks.now());
+ if (config._useUTC) {
+ return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
+ }
+ return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
+}
+
+// convert an array to a date.
+// the array should mirror the parameters below
+// note: all values past the year are optional and will default to the lowest possible value.
+// [year, month, day , hour, minute, second, millisecond]
+function configFromArray (config) {
+ var i, date, input = [], currentDate, yearToUse;
+
+ if (config._d) {
+ return;
+ }
+
+ currentDate = currentDateArray(config);
+
+ //compute day of the year from weeks and weekdays
+ if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+ dayOfYearFromWeekInfo(config);
+ }
+
+ //if the day of the year is set, figure out what it is
+ if (config._dayOfYear != null) {
+ yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
+
+ if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
+ getParsingFlags(config)._overflowDayOfYear = true;
+ }
+
+ date = createUTCDate(yearToUse, 0, config._dayOfYear);
+ config._a[MONTH] = date.getUTCMonth();
+ config._a[DATE] = date.getUTCDate();
+ }
+
+ // Default to current date.
+ // * if no year, month, day of month are given, default to today
+ // * if day of month is given, default month and year
+ // * if month is given, default only year
+ // * if year is given, don't default anything
+ for (i = 0; i < 3 && config._a[i] == null; ++i) {
+ config._a[i] = input[i] = currentDate[i];
+ }
+
+ // Zero out whatever was not defaulted, including time
+ for (; i < 7; i++) {
+ config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+ }
+
+ // Check for 24:00:00.000
+ if (config._a[HOUR] === 24 &&
+ config._a[MINUTE] === 0 &&
+ config._a[SECOND] === 0 &&
+ config._a[MILLISECOND] === 0) {
+ config._nextDay = true;
+ config._a[HOUR] = 0;
+ }
+
+ config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
+ // Apply timezone offset from input. The actual utcOffset can be changed
+ // with parseZone.
+ if (config._tzm != null) {
+ config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+ }
+
+ if (config._nextDay) {
+ config._a[HOUR] = 24;
+ }
+
+ // check for mismatching day of week
+ if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== config._d.getDay()) {
+ getParsingFlags(config).weekdayMismatch = true;
+ }
+}
+
+function dayOfYearFromWeekInfo(config) {
+ var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
+
+ w = config._w;
+ if (w.GG != null || w.W != null || w.E != null) {
+ dow = 1;
+ doy = 4;
+
+ // TODO: We need to take the current isoWeekYear, but that depends on
+ // how we interpret now (local, utc, fixed offset). So create
+ // a now version of current config (take local/utc/offset flags, and
+ // create now).
+ weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
+ week = defaults(w.W, 1);
+ weekday = defaults(w.E, 1);
+ if (weekday < 1 || weekday > 7) {
+ weekdayOverflow = true;
+ }
+ } else {
+ dow = config._locale._week.dow;
+ doy = config._locale._week.doy;
+
+ var curWeek = weekOfYear(createLocal(), dow, doy);
+
+ weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
+
+ // Default to current week.
+ week = defaults(w.w, curWeek.week);
+
+ if (w.d != null) {
+ // weekday -- low day numbers are considered next week
+ weekday = w.d;
+ if (weekday < 0 || weekday > 6) {
+ weekdayOverflow = true;
+ }
+ } else if (w.e != null) {
+ // local weekday -- counting starts from beginning of week
+ weekday = w.e + dow;
+ if (w.e < 0 || w.e > 6) {
+ weekdayOverflow = true;
+ }
+ } else {
+ // default to beginning of week
+ weekday = dow;
+ }
+ }
+ if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
+ getParsingFlags(config)._overflowWeeks = true;
+ } else if (weekdayOverflow != null) {
+ getParsingFlags(config)._overflowWeekday = true;
+ } else {
+ temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
+ config._a[YEAR] = temp.year;
+ config._dayOfYear = temp.dayOfYear;
+ }
+}
+
+// iso 8601 regex
+// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+
+var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
+
+var isoDates = [
+ ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+ ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+ ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+ ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+ ['YYYY-DDD', /\d{4}-\d{3}/],
+ ['YYYY-MM', /\d{4}-\d\d/, false],
+ ['YYYYYYMMDD', /[+-]\d{10}/],
+ ['YYYYMMDD', /\d{8}/],
+ // YYYYMM is NOT allowed by the standard
+ ['GGGG[W]WWE', /\d{4}W\d{3}/],
+ ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+ ['YYYYDDD', /\d{7}/]
+];
+
+// iso time formats and regexes
+var isoTimes = [
+ ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+ ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+ ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+ ['HH:mm', /\d\d:\d\d/],
+ ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+ ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+ ['HHmmss', /\d\d\d\d\d\d/],
+ ['HHmm', /\d\d\d\d/],
+ ['HH', /\d\d/]
+];
+
+var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
+
+// date from iso format
+function configFromISO(config) {
+ var i, l,
+ string = config._i,
+ match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+ allowTime, dateFormat, timeFormat, tzFormat;
+
+ if (match) {
+ getParsingFlags(config).iso = true;
+
+ for (i = 0, l = isoDates.length; i < l; i++) {
+ if (isoDates[i][1].exec(match[1])) {
+ dateFormat = isoDates[i][0];
+ allowTime = isoDates[i][2] !== false;
+ break;
+ }
+ }
+ if (dateFormat == null) {
+ config._isValid = false;
+ return;
+ }
+ if (match[3]) {
+ for (i = 0, l = isoTimes.length; i < l; i++) {
+ if (isoTimes[i][1].exec(match[3])) {
+ // match[2] should be 'T' or space
+ timeFormat = (match[2] || ' ') + isoTimes[i][0];
+ break;
+ }
+ }
+ if (timeFormat == null) {
+ config._isValid = false;
+ return;
+ }
+ }
+ if (!allowTime && timeFormat != null) {
+ config._isValid = false;
+ return;
+ }
+ if (match[4]) {
+ if (tzRegex.exec(match[4])) {
+ tzFormat = 'Z';
+ } else {
+ config._isValid = false;
+ return;
+ }
+ }
+ config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+ configFromStringAndFormat(config);
+ } else {
+ config._isValid = false;
+ }
+}
+
+// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
+var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
+
+function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
+ var result = [
+ untruncateYear(yearStr),
+ defaultLocaleMonthsShort.indexOf(monthStr),
+ parseInt(dayStr, 10),
+ parseInt(hourStr, 10),
+ parseInt(minuteStr, 10)
+ ];
+
+ if (secondStr) {
+ result.push(parseInt(secondStr, 10));
+ }
+
+ return result;
+}
+
+function untruncateYear(yearStr) {
+ var year = parseInt(yearStr, 10);
+ if (year <= 49) {
+ return 2000 + year;
+ } else if (year <= 999) {
+ return 1900 + year;
+ }
+ return year;
+}
+
+function preprocessRFC2822(s) {
+ // Remove comments and folding whitespace and replace multiple-spaces with a single space
+ return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim();
+}
+
+function checkWeekday(weekdayStr, parsedInput, config) {
+ if (weekdayStr) {
+ // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
+ var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
+ weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
+ if (weekdayProvided !== weekdayActual) {
+ getParsingFlags(config).weekdayMismatch = true;
+ config._isValid = false;
+ return false;
+ }
+ }
+ return true;
+}
+
+var obsOffsets = {
+ UT: 0,
+ GMT: 0,
+ EDT: -4 * 60,
+ EST: -5 * 60,
+ CDT: -5 * 60,
+ CST: -6 * 60,
+ MDT: -6 * 60,
+ MST: -7 * 60,
+ PDT: -7 * 60,
+ PST: -8 * 60
+};
+
+function calculateOffset(obsOffset, militaryOffset, numOffset) {
+ if (obsOffset) {
+ return obsOffsets[obsOffset];
+ } else if (militaryOffset) {
+ // the only allowed military tz is Z
+ return 0;
+ } else {
+ var hm = parseInt(numOffset, 10);
+ var m = hm % 100, h = (hm - m) / 100;
+ return h * 60 + m;
+ }
+}
+
+// date and time from ref 2822 format
+function configFromRFC2822(config) {
+ var match = rfc2822.exec(preprocessRFC2822(config._i));
+ if (match) {
+ var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
+ if (!checkWeekday(match[1], parsedArray, config)) {
+ return;
+ }
+
+ config._a = parsedArray;
+ config._tzm = calculateOffset(match[8], match[9], match[10]);
+
+ config._d = createUTCDate.apply(null, config._a);
+ config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+
+ getParsingFlags(config).rfc2822 = true;
+ } else {
+ config._isValid = false;
+ }
+}
+
+// date from iso format or fallback
+function configFromString(config) {
+ var matched = aspNetJsonRegex.exec(config._i);
+
+ if (matched !== null) {
+ config._d = new Date(+matched[1]);
+ return;
+ }
+
+ configFromISO(config);
+ if (config._isValid === false) {
+ delete config._isValid;
+ } else {
+ return;
+ }
+
+ configFromRFC2822(config);
+ if (config._isValid === false) {
+ delete config._isValid;
+ } else {
+ return;
+ }
+
+ // Final attempt, use Input Fallback
+ hooks.createFromInputFallback(config);
+}
+
+hooks.createFromInputFallback = deprecate(
+ 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
+ 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
+ 'discouraged and will be removed in an upcoming major release. Please refer to ' +
+ 'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
+ function (config) {
+ config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+ }
+);
+
+// constant that refers to the ISO standard
+hooks.ISO_8601 = function () {};
+
+// constant that refers to the RFC 2822 form
+hooks.RFC_2822 = function () {};
+
+// date from string and format string
+function configFromStringAndFormat(config) {
+ // TODO: Move this to another part of the creation flow to prevent circular deps
+ if (config._f === hooks.ISO_8601) {
+ configFromISO(config);
+ return;
+ }
+ if (config._f === hooks.RFC_2822) {
+ configFromRFC2822(config);
+ return;
+ }
+ config._a = [];
+ getParsingFlags(config).empty = true;
+
+ // This array is used to make a Date, either with `new Date` or `Date.UTC`
+ var string = '' + config._i,
+ i, parsedInput, tokens, token, skipped,
+ stringLength = string.length,
+ totalParsedInputLength = 0;
+
+ tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
+
+ for (i = 0; i < tokens.length; i++) {
+ token = tokens[i];
+ parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
+ // console.log('token', token, 'parsedInput', parsedInput,
+ // 'regex', getParseRegexForToken(token, config));
+ if (parsedInput) {
+ skipped = string.substr(0, string.indexOf(parsedInput));
+ if (skipped.length > 0) {
+ getParsingFlags(config).unusedInput.push(skipped);
+ }
+ string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+ totalParsedInputLength += parsedInput.length;
+ }
+ // don't parse if it's not a known token
+ if (formatTokenFunctions[token]) {
+ if (parsedInput) {
+ getParsingFlags(config).empty = false;
+ }
+ else {
+ getParsingFlags(config).unusedTokens.push(token);
+ }
+ addTimeToArrayFromToken(token, parsedInput, config);
+ }
+ else if (config._strict && !parsedInput) {
+ getParsingFlags(config).unusedTokens.push(token);
+ }
+ }
+
+ // add remaining unparsed input length to the string
+ getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
+ if (string.length > 0) {
+ getParsingFlags(config).unusedInput.push(string);
+ }
+
+ // clear _12h flag if hour is <= 12
+ if (config._a[HOUR] <= 12 &&
+ getParsingFlags(config).bigHour === true &&
+ config._a[HOUR] > 0) {
+ getParsingFlags(config).bigHour = undefined;
+ }
+
+ getParsingFlags(config).parsedDateParts = config._a.slice(0);
+ getParsingFlags(config).meridiem = config._meridiem;
+ // handle meridiem
+ config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
+
+ configFromArray(config);
+ checkOverflow(config);
+}
+
+
+function meridiemFixWrap (locale, hour, meridiem) {
+ var isPm;
+
+ if (meridiem == null) {
+ // nothing to do
+ return hour;
+ }
+ if (locale.meridiemHour != null) {
+ return locale.meridiemHour(hour, meridiem);
+ } else if (locale.isPM != null) {
+ // Fallback
+ isPm = locale.isPM(meridiem);
+ if (isPm && hour < 12) {
+ hour += 12;
+ }
+ if (!isPm && hour === 12) {
+ hour = 0;
+ }
+ return hour;
+ } else {
+ // this is not supposed to happen
+ return hour;
+ }
+}
+
+// date from string and array of format strings
+function configFromStringAndArray(config) {
+ var tempConfig,
+ bestMoment,
+
+ scoreToBeat,
+ i,
+ currentScore;
+
+ if (config._f.length === 0) {
+ getParsingFlags(config).invalidFormat = true;
+ config._d = new Date(NaN);
+ return;
+ }
+
+ for (i = 0; i < config._f.length; i++) {
+ currentScore = 0;
+ tempConfig = copyConfig({}, config);
+ if (config._useUTC != null) {
+ tempConfig._useUTC = config._useUTC;
+ }
+ tempConfig._f = config._f[i];
+ configFromStringAndFormat(tempConfig);
+
+ if (!isValid(tempConfig)) {
+ continue;
+ }
+
+ // if there is any input that was not parsed add a penalty for that format
+ currentScore += getParsingFlags(tempConfig).charsLeftOver;
+
+ //or tokens
+ currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
+
+ getParsingFlags(tempConfig).score = currentScore;
+
+ if (scoreToBeat == null || currentScore < scoreToBeat) {
+ scoreToBeat = currentScore;
+ bestMoment = tempConfig;
+ }
+ }
+
+ extend(config, bestMoment || tempConfig);
+}
+
+function configFromObject(config) {
+ if (config._d) {
+ return;
+ }
+
+ var i = normalizeObjectUnits(config._i);
+ config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
+ return obj && parseInt(obj, 10);
+ });
+
+ configFromArray(config);
+}
+
+function createFromConfig (config) {
+ var res = new Moment(checkOverflow(prepareConfig(config)));
+ if (res._nextDay) {
+ // Adding is smart enough around DST
+ res.add(1, 'd');
+ res._nextDay = undefined;
+ }
+
+ return res;
+}
+
+function prepareConfig (config) {
+ var input = config._i,
+ format = config._f;
+
+ config._locale = config._locale || getLocale(config._l);
+
+ if (input === null || (format === undefined && input === '')) {
+ return createInvalid({nullInput: true});
+ }
+
+ if (typeof input === 'string') {
+ config._i = input = config._locale.preparse(input);
+ }
+
+ if (isMoment(input)) {
+ return new Moment(checkOverflow(input));
+ } else if (isDate(input)) {
+ config._d = input;
+ } else if (isArray(format)) {
+ configFromStringAndArray(config);
+ } else if (format) {
+ configFromStringAndFormat(config);
+ } else {
+ configFromInput(config);
+ }
+
+ if (!isValid(config)) {
+ config._d = null;
+ }
+
+ return config;
+}
+
+function configFromInput(config) {
+ var input = config._i;
+ if (isUndefined(input)) {
+ config._d = new Date(hooks.now());
+ } else if (isDate(input)) {
+ config._d = new Date(input.valueOf());
+ } else if (typeof input === 'string') {
+ configFromString(config);
+ } else if (isArray(input)) {
+ config._a = map(input.slice(0), function (obj) {
+ return parseInt(obj, 10);
+ });
+ configFromArray(config);
+ } else if (isObject(input)) {
+ configFromObject(config);
+ } else if (isNumber(input)) {
+ // from milliseconds
+ config._d = new Date(input);
+ } else {
+ hooks.createFromInputFallback(config);
+ }
+}
+
+function createLocalOrUTC (input, format, locale, strict, isUTC) {
+ var c = {};
+
+ if (locale === true || locale === false) {
+ strict = locale;
+ locale = undefined;
+ }
+
+ if ((isObject(input) && isObjectEmpty(input)) ||
+ (isArray(input) && input.length === 0)) {
+ input = undefined;
+ }
+ // object construction must be done this way.
+ // https://github.com/moment/moment/issues/1423
+ c._isAMomentObject = true;
+ c._useUTC = c._isUTC = isUTC;
+ c._l = locale;
+ c._i = input;
+ c._f = format;
+ c._strict = strict;
+
+ return createFromConfig(c);
+}
+
+function createLocal (input, format, locale, strict) {
+ return createLocalOrUTC(input, format, locale, strict, false);
+}
+
+var prototypeMin = deprecate(
+ 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
+ function () {
+ var other = createLocal.apply(null, arguments);
+ if (this.isValid() && other.isValid()) {
+ return other < this ? this : other;
+ } else {
+ return createInvalid();
+ }
+ }
+);
+
+var prototypeMax = deprecate(
+ 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
+ function () {
+ var other = createLocal.apply(null, arguments);
+ if (this.isValid() && other.isValid()) {
+ return other > this ? this : other;
+ } else {
+ return createInvalid();
+ }
+ }
+);
+
+// Pick a moment m from moments so that m[fn](other) is true for all
+// other. This relies on the function fn to be transitive.
+//
+// moments should either be an array of moment objects or an array, whose
+// first element is an array of moment objects.
+function pickBy(fn, moments) {
+ var res, i;
+ if (moments.length === 1 && isArray(moments[0])) {
+ moments = moments[0];
+ }
+ if (!moments.length) {
+ return createLocal();
+ }
+ res = moments[0];
+ for (i = 1; i < moments.length; ++i) {
+ if (!moments[i].isValid() || moments[i][fn](res)) {
+ res = moments[i];
+ }
+ }
+ return res;
+}
+
+// TODO: Use [].sort instead?
+function min () {
+ var args = [].slice.call(arguments, 0);
+
+ return pickBy('isBefore', args);
+}
+
+function max () {
+ var args = [].slice.call(arguments, 0);
+
+ return pickBy('isAfter', args);
+}
+
+var now = function () {
+ return Date.now ? Date.now() : +(new Date());
+};
+
+var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
+
+function isDurationValid(m) {
+ for (var key in m) {
+ if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
+ return false;
+ }
+ }
+
+ var unitHasDecimal = false;
+ for (var i = 0; i < ordering.length; ++i) {
+ if (m[ordering[i]]) {
+ if (unitHasDecimal) {
+ return false; // only allow non-integers for smallest unit
+ }
+ if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
+ unitHasDecimal = true;
+ }
+ }
+ }
+
+ return true;
+}
+
+function isValid$1() {
+ return this._isValid;
+}
+
+function createInvalid$1() {
+ return createDuration(NaN);
+}
+
+function Duration (duration) {
+ var normalizedInput = normalizeObjectUnits(duration),
+ years = normalizedInput.year || 0,
+ quarters = normalizedInput.quarter || 0,
+ months = normalizedInput.month || 0,
+ weeks = normalizedInput.week || 0,
+ days = normalizedInput.day || 0,
+ hours = normalizedInput.hour || 0,
+ minutes = normalizedInput.minute || 0,
+ seconds = normalizedInput.second || 0,
+ milliseconds = normalizedInput.millisecond || 0;
+
+ this._isValid = isDurationValid(normalizedInput);
+
+ // representation for dateAddRemove
+ this._milliseconds = +milliseconds +
+ seconds * 1e3 + // 1000
+ minutes * 6e4 + // 1000 * 60
+ hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
+ // Because of dateAddRemove treats 24 hours as different from a
+ // day when working around DST, we need to store them separately
+ this._days = +days +
+ weeks * 7;
+ // It is impossible to translate months into days without knowing
+ // which months you are are talking about, so we have to store
+ // it separately.
+ this._months = +months +
+ quarters * 3 +
+ years * 12;
+
+ this._data = {};
+
+ this._locale = getLocale();
+
+ this._bubble();
+}
+
+function isDuration (obj) {
+ return obj instanceof Duration;
+}
+
+function absRound (number) {
+ if (number < 0) {
+ return Math.round(-1 * number) * -1;
+ } else {
+ return Math.round(number);
+ }
+}
+
+// FORMATTING
+
+function offset (token, separator) {
+ addFormatToken(token, 0, 0, function () {
+ var offset = this.utcOffset();
+ var sign = '+';
+ if (offset < 0) {
+ offset = -offset;
+ sign = '-';
+ }
+ return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
+ });
+}
+
+offset('Z', ':');
+offset('ZZ', '');
+
+// PARSING
+
+addRegexToken('Z', matchShortOffset);
+addRegexToken('ZZ', matchShortOffset);
+addParseToken(['Z', 'ZZ'], function (input, array, config) {
+ config._useUTC = true;
+ config._tzm = offsetFromString(matchShortOffset, input);
+});
+
+// HELPERS
+
+// timezone chunker
+// '+10:00' > ['10', '00']
+// '-1530' > ['-15', '30']
+var chunkOffset = /([\+\-]|\d\d)/gi;
+
+function offsetFromString(matcher, string) {
+ var matches = (string || '').match(matcher);
+
+ if (matches === null) {
+ return null;
+ }
+
+ var chunk = matches[matches.length - 1] || [];
+ var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+ var minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+ return minutes === 0 ?
+ 0 :
+ parts[0] === '+' ? minutes : -minutes;
+}
+
+// Return a moment from input, that is local/utc/zone equivalent to model.
+function cloneWithOffset(input, model) {
+ var res, diff;
+ if (model._isUTC) {
+ res = model.clone();
+ diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
+ // Use low-level api, because this fn is low-level api.
+ res._d.setTime(res._d.valueOf() + diff);
+ hooks.updateOffset(res, false);
+ return res;
+ } else {
+ return createLocal(input).local();
+ }
+}
+
+function getDateOffset (m) {
+ // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+ // https://github.com/moment/moment/pull/1871
+ return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
+}
+
+// HOOKS
+
+// This function will be called whenever a moment is mutated.
+// It is intended to keep the offset in sync with the timezone.
+hooks.updateOffset = function () {};
+
+// MOMENTS
+
+// keepLocalTime = true means only change the timezone, without
+// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+// +0200, so we adjust the time as needed, to be valid.
+//
+// Keeping the time actually adds/subtracts (one hour)
+// from the actual represented time. That is why we call updateOffset
+// a second time. In case it wants us to change the offset again
+// _changeInProgress == true case, then we have to adjust, because
+// there is no such time in the given timezone.
+function getSetOffset (input, keepLocalTime, keepMinutes) {
+ var offset = this._offset || 0,
+ localAdjust;
+ if (!this.isValid()) {
+ return input != null ? this : NaN;
+ }
+ if (input != null) {
+ if (typeof input === 'string') {
+ input = offsetFromString(matchShortOffset, input);
+ if (input === null) {
+ return this;
+ }
+ } else if (Math.abs(input) < 16 && !keepMinutes) {
+ input = input * 60;
+ }
+ if (!this._isUTC && keepLocalTime) {
+ localAdjust = getDateOffset(this);
+ }
+ this._offset = input;
+ this._isUTC = true;
+ if (localAdjust != null) {
+ this.add(localAdjust, 'm');
+ }
+ if (offset !== input) {
+ if (!keepLocalTime || this._changeInProgress) {
+ addSubtract(this, createDuration(input - offset, 'm'), 1, false);
+ } else if (!this._changeInProgress) {
+ this._changeInProgress = true;
+ hooks.updateOffset(this, true);
+ this._changeInProgress = null;
+ }
+ }
+ return this;
+ } else {
+ return this._isUTC ? offset : getDateOffset(this);
+ }
+}
+
+function getSetZone (input, keepLocalTime) {
+ if (input != null) {
+ if (typeof input !== 'string') {
+ input = -input;
+ }
+
+ this.utcOffset(input, keepLocalTime);
+
+ return this;
+ } else {
+ return -this.utcOffset();
+ }
+}
+
+function setOffsetToUTC (keepLocalTime) {
+ return this.utcOffset(0, keepLocalTime);
+}
+
+function setOffsetToLocal (keepLocalTime) {
+ if (this._isUTC) {
+ this.utcOffset(0, keepLocalTime);
+ this._isUTC = false;
+
+ if (keepLocalTime) {
+ this.subtract(getDateOffset(this), 'm');
+ }
+ }
+ return this;
+}
+
+function setOffsetToParsedOffset () {
+ if (this._tzm != null) {
+ this.utcOffset(this._tzm, false, true);
+ } else if (typeof this._i === 'string') {
+ var tZone = offsetFromString(matchOffset, this._i);
+ if (tZone != null) {
+ this.utcOffset(tZone);
+ }
+ else {
+ this.utcOffset(0, true);
+ }
+ }
+ return this;
+}
+
+function hasAlignedHourOffset (input) {
+ if (!this.isValid()) {
+ return false;
+ }
+ input = input ? createLocal(input).utcOffset() : 0;
+
+ return (this.utcOffset() - input) % 60 === 0;
+}
+
+function isDaylightSavingTime () {
+ return (
+ this.utcOffset() > this.clone().month(0).utcOffset() ||
+ this.utcOffset() > this.clone().month(5).utcOffset()
+ );
+}
+
+function isDaylightSavingTimeShifted () {
+ if (!isUndefined(this._isDSTShifted)) {
+ return this._isDSTShifted;
+ }
+
+ var c = {};
+
+ copyConfig(c, this);
+ c = prepareConfig(c);
+
+ if (c._a) {
+ var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
+ this._isDSTShifted = this.isValid() &&
+ compareArrays(c._a, other.toArray()) > 0;
+ } else {
+ this._isDSTShifted = false;
+ }
+
+ return this._isDSTShifted;
+}
+
+function isLocal () {
+ return this.isValid() ? !this._isUTC : false;
+}
+
+function isUtcOffset () {
+ return this.isValid() ? this._isUTC : false;
+}
+
+function isUtc () {
+ return this.isValid() ? this._isUTC && this._offset === 0 : false;
+}
+
+// ASP.NET json date format regex
+var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
+
+// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+// and further modified to allow for strings containing both week and day
+var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
+
+function createDuration (input, key) {
+ var duration = input,
+ // matching against regexp is expensive, do it on demand
+ match = null,
+ sign,
+ ret,
+ diffRes;
+
+ if (isDuration(input)) {
+ duration = {
+ ms : input._milliseconds,
+ d : input._days,
+ M : input._months
+ };
+ } else if (isNumber(input)) {
+ duration = {};
+ if (key) {
+ duration[key] = input;
+ } else {
+ duration.milliseconds = input;
+ }
+ } else if (!!(match = aspNetRegex.exec(input))) {
+ sign = (match[1] === '-') ? -1 : 1;
+ duration = {
+ y : 0,
+ d : toInt(match[DATE]) * sign,
+ h : toInt(match[HOUR]) * sign,
+ m : toInt(match[MINUTE]) * sign,
+ s : toInt(match[SECOND]) * sign,
+ ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
+ };
+ } else if (!!(match = isoRegex.exec(input))) {
+ sign = (match[1] === '-') ? -1 : (match[1] === '+') ? 1 : 1;
+ duration = {
+ y : parseIso(match[2], sign),
+ M : parseIso(match[3], sign),
+ w : parseIso(match[4], sign),
+ d : parseIso(match[5], sign),
+ h : parseIso(match[6], sign),
+ m : parseIso(match[7], sign),
+ s : parseIso(match[8], sign)
+ };
+ } else if (duration == null) {// checks for null or undefined
+ duration = {};
+ } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
+ diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
+
+ duration = {};
+ duration.ms = diffRes.milliseconds;
+ duration.M = diffRes.months;
+ }
+
+ ret = new Duration(duration);
+
+ if (isDuration(input) && hasOwnProp(input, '_locale')) {
+ ret._locale = input._locale;
+ }
+
+ return ret;
+}
+
+createDuration.fn = Duration.prototype;
+createDuration.invalid = createInvalid$1;
+
+function parseIso (inp, sign) {
+ // We'd normally use ~~inp for this, but unfortunately it also
+ // converts floats to ints.
+ // inp may be undefined, so careful calling replace on it.
+ var res = inp && parseFloat(inp.replace(',', '.'));
+ // apply sign while we're at it
+ return (isNaN(res) ? 0 : res) * sign;
+}
+
+function positiveMomentsDifference(base, other) {
+ var res = {milliseconds: 0, months: 0};
+
+ res.months = other.month() - base.month() +
+ (other.year() - base.year()) * 12;
+ if (base.clone().add(res.months, 'M').isAfter(other)) {
+ --res.months;
+ }
+
+ res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
+
+ return res;
+}
+
+function momentsDifference(base, other) {
+ var res;
+ if (!(base.isValid() && other.isValid())) {
+ return {milliseconds: 0, months: 0};
+ }
+
+ other = cloneWithOffset(other, base);
+ if (base.isBefore(other)) {
+ res = positiveMomentsDifference(base, other);
+ } else {
+ res = positiveMomentsDifference(other, base);
+ res.milliseconds = -res.milliseconds;
+ res.months = -res.months;
+ }
+
+ return res;
+}
+
+// TODO: remove 'name' arg after deprecation is removed
+function createAdder(direction, name) {
+ return function (val, period) {
+ var dur, tmp;
+ //invert the arguments, but complain about it
+ if (period !== null && !isNaN(+period)) {
+ deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
+ 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
+ tmp = val; val = period; period = tmp;
+ }
+
+ val = typeof val === 'string' ? +val : val;
+ dur = createDuration(val, period);
+ addSubtract(this, dur, direction);
+ return this;
+ };
+}
+
+function addSubtract (mom, duration, isAdding, updateOffset) {
+ var milliseconds = duration._milliseconds,
+ days = absRound(duration._days),
+ months = absRound(duration._months);
+
+ if (!mom.isValid()) {
+ // No op
+ return;
+ }
+
+ updateOffset = updateOffset == null ? true : updateOffset;
+
+ if (months) {
+ setMonth(mom, get(mom, 'Month') + months * isAdding);
+ }
+ if (days) {
+ set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
+ }
+ if (milliseconds) {
+ mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
+ }
+ if (updateOffset) {
+ hooks.updateOffset(mom, days || months);
+ }
+}
+
+var add = createAdder(1, 'add');
+var subtract = createAdder(-1, 'subtract');
+
+function getCalendarFormat(myMoment, now) {
+ var diff = myMoment.diff(now, 'days', true);
+ return diff < -6 ? 'sameElse' :
+ diff < -1 ? 'lastWeek' :
+ diff < 0 ? 'lastDay' :
+ diff < 1 ? 'sameDay' :
+ diff < 2 ? 'nextDay' :
+ diff < 7 ? 'nextWeek' : 'sameElse';
+}
+
+function calendar$1 (time, formats) {
+ // We want to compare the start of today, vs this.
+ // Getting start-of-today depends on whether we're local/utc/offset or not.
+ var now = time || createLocal(),
+ sod = cloneWithOffset(now, this).startOf('day'),
+ format = hooks.calendarFormat(this, sod) || 'sameElse';
+
+ var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
+
+ return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
+}
+
+function clone () {
+ return new Moment(this);
+}
+
+function isAfter (input, units) {
+ var localInput = isMoment(input) ? input : createLocal(input);
+ if (!(this.isValid() && localInput.isValid())) {
+ return false;
+ }
+ units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+ if (units === 'millisecond') {
+ return this.valueOf() > localInput.valueOf();
+ } else {
+ return localInput.valueOf() < this.clone().startOf(units).valueOf();
+ }
+}
+
+function isBefore (input, units) {
+ var localInput = isMoment(input) ? input : createLocal(input);
+ if (!(this.isValid() && localInput.isValid())) {
+ return false;
+ }
+ units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+ if (units === 'millisecond') {
+ return this.valueOf() < localInput.valueOf();
+ } else {
+ return this.clone().endOf(units).valueOf() < localInput.valueOf();
+ }
+}
+
+function isBetween (from, to, units, inclusivity) {
+ inclusivity = inclusivity || '()';
+ return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
+ (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
+}
+
+function isSame (input, units) {
+ var localInput = isMoment(input) ? input : createLocal(input),
+ inputMs;
+ if (!(this.isValid() && localInput.isValid())) {
+ return false;
+ }
+ units = normalizeUnits(units || 'millisecond');
+ if (units === 'millisecond') {
+ return this.valueOf() === localInput.valueOf();
+ } else {
+ inputMs = localInput.valueOf();
+ return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
+ }
+}
+
+function isSameOrAfter (input, units) {
+ return this.isSame(input, units) || this.isAfter(input,units);
+}
+
+function isSameOrBefore (input, units) {
+ return this.isSame(input, units) || this.isBefore(input,units);
+}
+
+function diff (input, units, asFloat) {
+ var that,
+ zoneDelta,
+ delta, output;
+
+ if (!this.isValid()) {
+ return NaN;
+ }
+
+ that = cloneWithOffset(input, this);
+
+ if (!that.isValid()) {
+ return NaN;
+ }
+
+ zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
+
+ units = normalizeUnits(units);
+
+ switch (units) {
+ case 'year': output = monthDiff(this, that) / 12; break;
+ case 'month': output = monthDiff(this, that); break;
+ case 'quarter': output = monthDiff(this, that) / 3; break;
+ case 'second': output = (this - that) / 1e3; break; // 1000
+ case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
+ case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
+ case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
+ case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
+ default: output = this - that;
+ }
+
+ return asFloat ? output : absFloor(output);
+}
+
+function monthDiff (a, b) {
+ // difference in months
+ var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
+ // b is in (anchor - 1 month, anchor + 1 month)
+ anchor = a.clone().add(wholeMonthDiff, 'months'),
+ anchor2, adjust;
+
+ if (b - anchor < 0) {
+ anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+ // linear across the month
+ adjust = (b - anchor) / (anchor - anchor2);
+ } else {
+ anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+ // linear across the month
+ adjust = (b - anchor) / (anchor2 - anchor);
+ }
+
+ //check for negative zero, return zero if negative zero
+ return -(wholeMonthDiff + adjust) || 0;
+}
+
+hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
+
+function toString () {
+ return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+}
+
+function toISOString() {
+ if (!this.isValid()) {
+ return null;
+ }
+ var m = this.clone().utc();
+ if (m.year() < 0 || m.year() > 9999) {
+ return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+ }
+ if (isFunction(Date.prototype.toISOString)) {
+ // native implementation is ~50x faster, use it when we can
+ return this.toDate().toISOString();
+ }
+ return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+}
+
+/**
+ * Return a human readable representation of a moment that can
+ * also be evaluated to get a new moment which is the same
+ *
+ * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
+ */
+function inspect () {
+ if (!this.isValid()) {
+ return 'moment.invalid(/* ' + this._i + ' */)';
+ }
+ var func = 'moment';
+ var zone = '';
+ if (!this.isLocal()) {
+ func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
+ zone = 'Z';
+ }
+ var prefix = '[' + func + '("]';
+ var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
+ var datetime = '-MM-DD[T]HH:mm:ss.SSS';
+ var suffix = zone + '[")]';
+
+ return this.format(prefix + year + datetime + suffix);
+}
+
+function format (inputString) {
+ if (!inputString) {
+ inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
+ }
+ var output = formatMoment(this, inputString);
+ return this.localeData().postformat(output);
+}
+
+function from (time, withoutSuffix) {
+ if (this.isValid() &&
+ ((isMoment(time) && time.isValid()) ||
+ createLocal(time).isValid())) {
+ return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
+ } else {
+ return this.localeData().invalidDate();
+ }
+}
+
+function fromNow (withoutSuffix) {
+ return this.from(createLocal(), withoutSuffix);
+}
+
+function to (time, withoutSuffix) {
+ if (this.isValid() &&
+ ((isMoment(time) && time.isValid()) ||
+ createLocal(time).isValid())) {
+ return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
+ } else {
+ return this.localeData().invalidDate();
+ }
+}
+
+function toNow (withoutSuffix) {
+ return this.to(createLocal(), withoutSuffix);
+}
+
+// If passed a locale key, it will set the locale for this
+// instance. Otherwise, it will return the locale configuration
+// variables for this instance.
+function locale (key) {
+ var newLocaleData;
+
+ if (key === undefined) {
+ return this._locale._abbr;
+ } else {
+ newLocaleData = getLocale(key);
+ if (newLocaleData != null) {
+ this._locale = newLocaleData;
+ }
+ return this;
+ }
+}
+
+var lang = deprecate(
+ 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+ function (key) {
+ if (key === undefined) {
+ return this.localeData();
+ } else {
+ return this.locale(key);
+ }
+ }
+);
+
+function localeData () {
+ return this._locale;
+}
+
+function startOf (units) {
+ units = normalizeUnits(units);
+ // the following switch intentionally omits break keywords
+ // to utilize falling through the cases.
+ switch (units) {
+ case 'year':
+ this.month(0);
+ /* falls through */
+ case 'quarter':
+ case 'month':
+ this.date(1);
+ /* falls through */
+ case 'week':
+ case 'isoWeek':
+ case 'day':
+ case 'date':
+ this.hours(0);
+ /* falls through */
+ case 'hour':
+ this.minutes(0);
+ /* falls through */
+ case 'minute':
+ this.seconds(0);
+ /* falls through */
+ case 'second':
+ this.milliseconds(0);
+ }
+
+ // weeks are a special case
+ if (units === 'week') {
+ this.weekday(0);
+ }
+ if (units === 'isoWeek') {
+ this.isoWeekday(1);
+ }
+
+ // quarters are also special
+ if (units === 'quarter') {
+ this.month(Math.floor(this.month() / 3) * 3);
+ }
+
+ return this;
+}
+
+function endOf (units) {
+ units = normalizeUnits(units);
+ if (units === undefined || units === 'millisecond') {
+ return this;
+ }
+
+ // 'date' is an alias for 'day', so it should be considered as such.
+ if (units === 'date') {
+ units = 'day';
+ }
+
+ return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
+}
+
+function valueOf () {
+ return this._d.valueOf() - ((this._offset || 0) * 60000);
+}
+
+function unix () {
+ return Math.floor(this.valueOf() / 1000);
+}
+
+function toDate () {
+ return new Date(this.valueOf());
+}
+
+function toArray () {
+ var m = this;
+ return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
+}
+
+function toObject () {
+ var m = this;
+ return {
+ years: m.year(),
+ months: m.month(),
+ date: m.date(),
+ hours: m.hours(),
+ minutes: m.minutes(),
+ seconds: m.seconds(),
+ milliseconds: m.milliseconds()
+ };
+}
+
+function toJSON () {
+ // new Date(NaN).toJSON() === null
+ return this.isValid() ? this.toISOString() : null;
+}
+
+function isValid$2 () {
+ return isValid(this);
+}
+
+function parsingFlags () {
+ return extend({}, getParsingFlags(this));
+}
+
+function invalidAt () {
+ return getParsingFlags(this).overflow;
+}
+
+function creationData() {
+ return {
+ input: this._i,
+ format: this._f,
+ locale: this._locale,
+ isUTC: this._isUTC,
+ strict: this._strict
+ };
+}
+
+// FORMATTING
+
+addFormatToken(0, ['gg', 2], 0, function () {
+ return this.weekYear() % 100;
+});
+
+addFormatToken(0, ['GG', 2], 0, function () {
+ return this.isoWeekYear() % 100;
+});
+
+function addWeekYearFormatToken (token, getter) {
+ addFormatToken(0, [token, token.length], 0, getter);
+}
+
+addWeekYearFormatToken('gggg', 'weekYear');
+addWeekYearFormatToken('ggggg', 'weekYear');
+addWeekYearFormatToken('GGGG', 'isoWeekYear');
+addWeekYearFormatToken('GGGGG', 'isoWeekYear');
+
+// ALIASES
+
+addUnitAlias('weekYear', 'gg');
+addUnitAlias('isoWeekYear', 'GG');
+
+// PRIORITY
+
+addUnitPriority('weekYear', 1);
+addUnitPriority('isoWeekYear', 1);
+
+
+// PARSING
+
+addRegexToken('G', matchSigned);
+addRegexToken('g', matchSigned);
+addRegexToken('GG', match1to2, match2);
+addRegexToken('gg', match1to2, match2);
+addRegexToken('GGGG', match1to4, match4);
+addRegexToken('gggg', match1to4, match4);
+addRegexToken('GGGGG', match1to6, match6);
+addRegexToken('ggggg', match1to6, match6);
+
+addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
+ week[token.substr(0, 2)] = toInt(input);
+});
+
+addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+ week[token] = hooks.parseTwoDigitYear(input);
+});
+
+// MOMENTS
+
+function getSetWeekYear (input) {
+ return getSetWeekYearHelper.call(this,
+ input,
+ this.week(),
+ this.weekday(),
+ this.localeData()._week.dow,
+ this.localeData()._week.doy);
+}
+
+function getSetISOWeekYear (input) {
+ return getSetWeekYearHelper.call(this,
+ input, this.isoWeek(), this.isoWeekday(), 1, 4);
+}
+
+function getISOWeeksInYear () {
+ return weeksInYear(this.year(), 1, 4);
+}
+
+function getWeeksInYear () {
+ var weekInfo = this.localeData()._week;
+ return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+}
+
+function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+ var weeksTarget;
+ if (input == null) {
+ return weekOfYear(this, dow, doy).year;
+ } else {
+ weeksTarget = weeksInYear(input, dow, doy);
+ if (week > weeksTarget) {
+ week = weeksTarget;
+ }
+ return setWeekAll.call(this, input, week, weekday, dow, doy);
+ }
+}
+
+function setWeekAll(weekYear, week, weekday, dow, doy) {
+ var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+ date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
+
+ this.year(date.getUTCFullYear());
+ this.month(date.getUTCMonth());
+ this.date(date.getUTCDate());
+ return this;
+}
+
+// FORMATTING
+
+addFormatToken('Q', 0, 'Qo', 'quarter');
+
+// ALIASES
+
+addUnitAlias('quarter', 'Q');
+
+// PRIORITY
+
+addUnitPriority('quarter', 7);
+
+// PARSING
+
+addRegexToken('Q', match1);
+addParseToken('Q', function (input, array) {
+ array[MONTH] = (toInt(input) - 1) * 3;
+});
+
+// MOMENTS
+
+function getSetQuarter (input) {
+ return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+}
+
+// FORMATTING
+
+addFormatToken('D', ['DD', 2], 'Do', 'date');
+
+// ALIASES
+
+addUnitAlias('date', 'D');
+
+// PRIOROITY
+addUnitPriority('date', 9);
+
+// PARSING
+
+addRegexToken('D', match1to2);
+addRegexToken('DD', match1to2, match2);
+addRegexToken('Do', function (isStrict, locale) {
+ // TODO: Remove "ordinalParse" fallback in next major release.
+ return isStrict ?
+ (locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
+ locale._dayOfMonthOrdinalParseLenient;
+});
+
+addParseToken(['D', 'DD'], DATE);
+addParseToken('Do', function (input, array) {
+ array[DATE] = toInt(input.match(match1to2)[0], 10);
+});
+
+// MOMENTS
+
+var getSetDayOfMonth = makeGetSet('Date', true);
+
+// FORMATTING
+
+addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
+
+// ALIASES
+
+addUnitAlias('dayOfYear', 'DDD');
+
+// PRIORITY
+addUnitPriority('dayOfYear', 4);
+
+// PARSING
+
+addRegexToken('DDD', match1to3);
+addRegexToken('DDDD', match3);
+addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+ config._dayOfYear = toInt(input);
+});
+
+// HELPERS
+
+// MOMENTS
+
+function getSetDayOfYear (input) {
+ var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
+ return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
+}
+
+// FORMATTING
+
+addFormatToken('m', ['mm', 2], 0, 'minute');
+
+// ALIASES
+
+addUnitAlias('minute', 'm');
+
+// PRIORITY
+
+addUnitPriority('minute', 14);
+
+// PARSING
+
+addRegexToken('m', match1to2);
+addRegexToken('mm', match1to2, match2);
+addParseToken(['m', 'mm'], MINUTE);
+
+// MOMENTS
+
+var getSetMinute = makeGetSet('Minutes', false);
+
+// FORMATTING
+
+addFormatToken('s', ['ss', 2], 0, 'second');
+
+// ALIASES
+
+addUnitAlias('second', 's');
+
+// PRIORITY
+
+addUnitPriority('second', 15);
+
+// PARSING
+
+addRegexToken('s', match1to2);
+addRegexToken('ss', match1to2, match2);
+addParseToken(['s', 'ss'], SECOND);
+
+// MOMENTS
+
+var getSetSecond = makeGetSet('Seconds', false);
+
+// FORMATTING
+
+addFormatToken('S', 0, 0, function () {
+ return ~~(this.millisecond() / 100);
+});
+
+addFormatToken(0, ['SS', 2], 0, function () {
+ return ~~(this.millisecond() / 10);
+});
+
+addFormatToken(0, ['SSS', 3], 0, 'millisecond');
+addFormatToken(0, ['SSSS', 4], 0, function () {
+ return this.millisecond() * 10;
+});
+addFormatToken(0, ['SSSSS', 5], 0, function () {
+ return this.millisecond() * 100;
+});
+addFormatToken(0, ['SSSSSS', 6], 0, function () {
+ return this.millisecond() * 1000;
+});
+addFormatToken(0, ['SSSSSSS', 7], 0, function () {
+ return this.millisecond() * 10000;
+});
+addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
+ return this.millisecond() * 100000;
+});
+addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
+ return this.millisecond() * 1000000;
+});
+
+
+// ALIASES
+
+addUnitAlias('millisecond', 'ms');
+
+// PRIORITY
+
+addUnitPriority('millisecond', 16);
+
+// PARSING
+
+addRegexToken('S', match1to3, match1);
+addRegexToken('SS', match1to3, match2);
+addRegexToken('SSS', match1to3, match3);
+
+var token;
+for (token = 'SSSS'; token.length <= 9; token += 'S') {
+ addRegexToken(token, matchUnsigned);
+}
+
+function parseMs(input, array) {
+ array[MILLISECOND] = toInt(('0.' + input) * 1000);
+}
+
+for (token = 'S'; token.length <= 9; token += 'S') {
+ addParseToken(token, parseMs);
+}
+// MOMENTS
+
+var getSetMillisecond = makeGetSet('Milliseconds', false);
+
+// FORMATTING
+
+addFormatToken('z', 0, 0, 'zoneAbbr');
+addFormatToken('zz', 0, 0, 'zoneName');
+
+// MOMENTS
+
+function getZoneAbbr () {
+ return this._isUTC ? 'UTC' : '';
+}
+
+function getZoneName () {
+ return this._isUTC ? 'Coordinated Universal Time' : '';
+}
+
+var proto = Moment.prototype;
+
+proto.add = add;
+proto.calendar = calendar$1;
+proto.clone = clone;
+proto.diff = diff;
+proto.endOf = endOf;
+proto.format = format;
+proto.from = from;
+proto.fromNow = fromNow;
+proto.to = to;
+proto.toNow = toNow;
+proto.get = stringGet;
+proto.invalidAt = invalidAt;
+proto.isAfter = isAfter;
+proto.isBefore = isBefore;
+proto.isBetween = isBetween;
+proto.isSame = isSame;
+proto.isSameOrAfter = isSameOrAfter;
+proto.isSameOrBefore = isSameOrBefore;
+proto.isValid = isValid$2;
+proto.lang = lang;
+proto.locale = locale;
+proto.localeData = localeData;
+proto.max = prototypeMax;
+proto.min = prototypeMin;
+proto.parsingFlags = parsingFlags;
+proto.set = stringSet;
+proto.startOf = startOf;
+proto.subtract = subtract;
+proto.toArray = toArray;
+proto.toObject = toObject;
+proto.toDate = toDate;
+proto.toISOString = toISOString;
+proto.inspect = inspect;
+proto.toJSON = toJSON;
+proto.toString = toString;
+proto.unix = unix;
+proto.valueOf = valueOf;
+proto.creationData = creationData;
+
+// Year
+proto.year = getSetYear;
+proto.isLeapYear = getIsLeapYear;
+
+// Week Year
+proto.weekYear = getSetWeekYear;
+proto.isoWeekYear = getSetISOWeekYear;
+
+// Quarter
+proto.quarter = proto.quarters = getSetQuarter;
+
+// Month
+proto.month = getSetMonth;
+proto.daysInMonth = getDaysInMonth;
+
+// Week
+proto.week = proto.weeks = getSetWeek;
+proto.isoWeek = proto.isoWeeks = getSetISOWeek;
+proto.weeksInYear = getWeeksInYear;
+proto.isoWeeksInYear = getISOWeeksInYear;
+
+// Day
+proto.date = getSetDayOfMonth;
+proto.day = proto.days = getSetDayOfWeek;
+proto.weekday = getSetLocaleDayOfWeek;
+proto.isoWeekday = getSetISODayOfWeek;
+proto.dayOfYear = getSetDayOfYear;
+
+// Hour
+proto.hour = proto.hours = getSetHour;
+
+// Minute
+proto.minute = proto.minutes = getSetMinute;
+
+// Second
+proto.second = proto.seconds = getSetSecond;
+
+// Millisecond
+proto.millisecond = proto.milliseconds = getSetMillisecond;
+
+// Offset
+proto.utcOffset = getSetOffset;
+proto.utc = setOffsetToUTC;
+proto.local = setOffsetToLocal;
+proto.parseZone = setOffsetToParsedOffset;
+proto.hasAlignedHourOffset = hasAlignedHourOffset;
+proto.isDST = isDaylightSavingTime;
+proto.isLocal = isLocal;
+proto.isUtcOffset = isUtcOffset;
+proto.isUtc = isUtc;
+proto.isUTC = isUtc;
+
+// Timezone
+proto.zoneAbbr = getZoneAbbr;
+proto.zoneName = getZoneName;
+
+// Deprecations
+proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
+proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
+proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
+proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
+proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
+
+function createUnix (input) {
+ return createLocal(input * 1000);
+}
+
+function createInZone () {
+ return createLocal.apply(null, arguments).parseZone();
+}
+
+function preParsePostFormat (string) {
+ return string;
+}
+
+var proto$1 = Locale.prototype;
+
+proto$1.calendar = calendar;
+proto$1.longDateFormat = longDateFormat;
+proto$1.invalidDate = invalidDate;
+proto$1.ordinal = ordinal;
+proto$1.preparse = preParsePostFormat;
+proto$1.postformat = preParsePostFormat;
+proto$1.relativeTime = relativeTime;
+proto$1.pastFuture = pastFuture;
+proto$1.set = set;
+
+// Month
+proto$1.months = localeMonths;
+proto$1.monthsShort = localeMonthsShort;
+proto$1.monthsParse = localeMonthsParse;
+proto$1.monthsRegex = monthsRegex;
+proto$1.monthsShortRegex = monthsShortRegex;
+
+// Week
+proto$1.week = localeWeek;
+proto$1.firstDayOfYear = localeFirstDayOfYear;
+proto$1.firstDayOfWeek = localeFirstDayOfWeek;
+
+// Day of Week
+proto$1.weekdays = localeWeekdays;
+proto$1.weekdaysMin = localeWeekdaysMin;
+proto$1.weekdaysShort = localeWeekdaysShort;
+proto$1.weekdaysParse = localeWeekdaysParse;
+
+proto$1.weekdaysRegex = weekdaysRegex;
+proto$1.weekdaysShortRegex = weekdaysShortRegex;
+proto$1.weekdaysMinRegex = weekdaysMinRegex;
+
+// Hours
+proto$1.isPM = localeIsPM;
+proto$1.meridiem = localeMeridiem;
+
+function get$1 (format, index, field, setter) {
+ var locale = getLocale();
+ var utc = createUTC().set(setter, index);
+ return locale[field](utc, format);
+}
+
+function listMonthsImpl (format, index, field) {
+ if (isNumber(format)) {
+ index = format;
+ format = undefined;
+ }
+
+ format = format || '';
+
+ if (index != null) {
+ return get$1(format, index, field, 'month');
+ }
+
+ var i;
+ var out = [];
+ for (i = 0; i < 12; i++) {
+ out[i] = get$1(format, i, field, 'month');
+ }
+ return out;
+}
+
+// ()
+// (5)
+// (fmt, 5)
+// (fmt)
+// (true)
+// (true, 5)
+// (true, fmt, 5)
+// (true, fmt)
+function listWeekdaysImpl (localeSorted, format, index, field) {
+ if (typeof localeSorted === 'boolean') {
+ if (isNumber(format)) {
+ index = format;
+ format = undefined;
+ }
+
+ format = format || '';
+ } else {
+ format = localeSorted;
+ index = format;
+ localeSorted = false;
+
+ if (isNumber(format)) {
+ index = format;
+ format = undefined;
+ }
+
+ format = format || '';
+ }
+
+ var locale = getLocale(),
+ shift = localeSorted ? locale._week.dow : 0;
+
+ if (index != null) {
+ return get$1(format, (index + shift) % 7, field, 'day');
+ }
+
+ var i;
+ var out = [];
+ for (i = 0; i < 7; i++) {
+ out[i] = get$1(format, (i + shift) % 7, field, 'day');
+ }
+ return out;
+}
+
+function listMonths (format, index) {
+ return listMonthsImpl(format, index, 'months');
+}
+
+function listMonthsShort (format, index) {
+ return listMonthsImpl(format, index, 'monthsShort');
+}
+
+function listWeekdays (localeSorted, format, index) {
+ return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
+}
+
+function listWeekdaysShort (localeSorted, format, index) {
+ return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
+}
+
+function listWeekdaysMin (localeSorted, format, index) {
+ return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
+}
+
+getSetGlobalLocale('en', {
+ dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
+ ordinal : function (number) {
+ var b = number % 10,
+ output = (toInt(number % 100 / 10) === 1) ? 'th' :
+ (b === 1) ? 'st' :
+ (b === 2) ? 'nd' :
+ (b === 3) ? 'rd' : 'th';
+ return number + output;
+ }
+});
+
+// Side effect imports
+hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
+hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
+
+var mathAbs = Math.abs;
+
+function abs () {
+ var data = this._data;
+
+ this._milliseconds = mathAbs(this._milliseconds);
+ this._days = mathAbs(this._days);
+ this._months = mathAbs(this._months);
+
+ data.milliseconds = mathAbs(data.milliseconds);
+ data.seconds = mathAbs(data.seconds);
+ data.minutes = mathAbs(data.minutes);
+ data.hours = mathAbs(data.hours);
+ data.months = mathAbs(data.months);
+ data.years = mathAbs(data.years);
+
+ return this;
+}
+
+function addSubtract$1 (duration, input, value, direction) {
+ var other = createDuration(input, value);
+
+ duration._milliseconds += direction * other._milliseconds;
+ duration._days += direction * other._days;
+ duration._months += direction * other._months;
+
+ return duration._bubble();
+}
+
+// supports only 2.0-style add(1, 's') or add(duration)
+function add$1 (input, value) {
+ return addSubtract$1(this, input, value, 1);
+}
+
+// supports only 2.0-style subtract(1, 's') or subtract(duration)
+function subtract$1 (input, value) {
+ return addSubtract$1(this, input, value, -1);
+}
+
+function absCeil (number) {
+ if (number < 0) {
+ return Math.floor(number);
+ } else {
+ return Math.ceil(number);
+ }
+}
+
+function bubble () {
+ var milliseconds = this._milliseconds;
+ var days = this._days;
+ var months = this._months;
+ var data = this._data;
+ var seconds, minutes, hours, years, monthsFromDays;
+
+ // if we have a mix of positive and negative values, bubble down first
+ // check: https://github.com/moment/moment/issues/2166
+ if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
+ (milliseconds <= 0 && days <= 0 && months <= 0))) {
+ milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
+ days = 0;
+ months = 0;
+ }
+
+ // The following code bubbles up values, see the tests for
+ // examples of what that means.
+ data.milliseconds = milliseconds % 1000;
+
+ seconds = absFloor(milliseconds / 1000);
+ data.seconds = seconds % 60;
+
+ minutes = absFloor(seconds / 60);
+ data.minutes = minutes % 60;
+
+ hours = absFloor(minutes / 60);
+ data.hours = hours % 24;
+
+ days += absFloor(hours / 24);
+
+ // convert days to months
+ monthsFromDays = absFloor(daysToMonths(days));
+ months += monthsFromDays;
+ days -= absCeil(monthsToDays(monthsFromDays));
+
+ // 12 months -> 1 year
+ years = absFloor(months / 12);
+ months %= 12;
+
+ data.days = days;
+ data.months = months;
+ data.years = years;
+
+ return this;
+}
+
+function daysToMonths (days) {
+ // 400 years have 146097 days (taking into account leap year rules)
+ // 400 years have 12 months === 4800
+ return days * 4800 / 146097;
+}
+
+function monthsToDays (months) {
+ // the reverse of daysToMonths
+ return months * 146097 / 4800;
+}
+
+function as (units) {
+ if (!this.isValid()) {
+ return NaN;
+ }
+ var days;
+ var months;
+ var milliseconds = this._milliseconds;
+
+ units = normalizeUnits(units);
+
+ if (units === 'month' || units === 'year') {
+ days = this._days + milliseconds / 864e5;
+ months = this._months + daysToMonths(days);
+ return units === 'month' ? months : months / 12;
+ } else {
+ // handle milliseconds separately because of floating point math errors (issue #1867)
+ days = this._days + Math.round(monthsToDays(this._months));
+ switch (units) {
+ case 'week' : return days / 7 + milliseconds / 6048e5;
+ case 'day' : return days + milliseconds / 864e5;
+ case 'hour' : return days * 24 + milliseconds / 36e5;
+ case 'minute' : return days * 1440 + milliseconds / 6e4;
+ case 'second' : return days * 86400 + milliseconds / 1000;
+ // Math.floor prevents floating point math errors here
+ case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
+ default: throw new Error('Unknown unit ' + units);
+ }
+ }
+}
+
+// TODO: Use this.as('ms')?
+function valueOf$1 () {
+ if (!this.isValid()) {
+ return NaN;
+ }
+ return (
+ this._milliseconds +
+ this._days * 864e5 +
+ (this._months % 12) * 2592e6 +
+ toInt(this._months / 12) * 31536e6
+ );
+}
+
+function makeAs (alias) {
+ return function () {
+ return this.as(alias);
+ };
+}
+
+var asMilliseconds = makeAs('ms');
+var asSeconds = makeAs('s');
+var asMinutes = makeAs('m');
+var asHours = makeAs('h');
+var asDays = makeAs('d');
+var asWeeks = makeAs('w');
+var asMonths = makeAs('M');
+var asYears = makeAs('y');
+
+function clone$1 () {
+ return createDuration(this);
+}
+
+function get$2 (units) {
+ units = normalizeUnits(units);
+ return this.isValid() ? this[units + 's']() : NaN;
+}
+
+function makeGetter(name) {
+ return function () {
+ return this.isValid() ? this._data[name] : NaN;
+ };
+}
+
+var milliseconds = makeGetter('milliseconds');
+var seconds = makeGetter('seconds');
+var minutes = makeGetter('minutes');
+var hours = makeGetter('hours');
+var days = makeGetter('days');
+var months = makeGetter('months');
+var years = makeGetter('years');
+
+function weeks () {
+ return absFloor(this.days() / 7);
+}
+
+var round = Math.round;
+var thresholds = {
+ ss: 44, // a few seconds to seconds
+ s : 45, // seconds to minute
+ m : 45, // minutes to hour
+ h : 22, // hours to day
+ d : 26, // days to month
+ M : 11 // months to year
+};
+
+// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+ return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+}
+
+function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
+ var duration = createDuration(posNegDuration).abs();
+ var seconds = round(duration.as('s'));
+ var minutes = round(duration.as('m'));
+ var hours = round(duration.as('h'));
+ var days = round(duration.as('d'));
+ var months = round(duration.as('M'));
+ var years = round(duration.as('y'));
+
+ var a = seconds <= thresholds.ss && ['s', seconds] ||
+ seconds < thresholds.s && ['ss', seconds] ||
+ minutes <= 1 && ['m'] ||
+ minutes < thresholds.m && ['mm', minutes] ||
+ hours <= 1 && ['h'] ||
+ hours < thresholds.h && ['hh', hours] ||
+ days <= 1 && ['d'] ||
+ days < thresholds.d && ['dd', days] ||
+ months <= 1 && ['M'] ||
+ months < thresholds.M && ['MM', months] ||
+ years <= 1 && ['y'] || ['yy', years];
+
+ a[2] = withoutSuffix;
+ a[3] = +posNegDuration > 0;
+ a[4] = locale;
+ return substituteTimeAgo.apply(null, a);
+}
+
+// This function allows you to set the rounding function for relative time strings
+function getSetRelativeTimeRounding (roundingFunction) {
+ if (roundingFunction === undefined) {
+ return round;
+ }
+ if (typeof(roundingFunction) === 'function') {
+ round = roundingFunction;
+ return true;
+ }
+ return false;
+}
+
+// This function allows you to set a threshold for relative time strings
+function getSetRelativeTimeThreshold (threshold, limit) {
+ if (thresholds[threshold] === undefined) {
+ return false;
+ }
+ if (limit === undefined) {
+ return thresholds[threshold];
+ }
+ thresholds[threshold] = limit;
+ if (threshold === 's') {
+ thresholds.ss = limit - 1;
+ }
+ return true;
+}
+
+function humanize (withSuffix) {
+ if (!this.isValid()) {
+ return this.localeData().invalidDate();
+ }
+
+ var locale = this.localeData();
+ var output = relativeTime$1(this, !withSuffix, locale);
+
+ if (withSuffix) {
+ output = locale.pastFuture(+this, output);
+ }
+
+ return locale.postformat(output);
+}
+
+var abs$1 = Math.abs;
+
+function sign(x) {
+ return ((x > 0) - (x < 0)) || +x;
+}
+
+function toISOString$1() {
+ // for ISO strings we do not use the normal bubbling rules:
+ // * milliseconds bubble up until they become hours
+ // * days do not bubble at all
+ // * months bubble up until they become years
+ // This is because there is no context-free conversion between hours and days
+ // (think of clock changes)
+ // and also not between days and months (28-31 days per month)
+ if (!this.isValid()) {
+ return this.localeData().invalidDate();
+ }
+
+ var seconds = abs$1(this._milliseconds) / 1000;
+ var days = abs$1(this._days);
+ var months = abs$1(this._months);
+ var minutes, hours, years;
+
+ // 3600 seconds -> 60 minutes -> 1 hour
+ minutes = absFloor(seconds / 60);
+ hours = absFloor(minutes / 60);
+ seconds %= 60;
+ minutes %= 60;
+
+ // 12 months -> 1 year
+ years = absFloor(months / 12);
+ months %= 12;
+
+
+ // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+ var Y = years;
+ var M = months;
+ var D = days;
+ var h = hours;
+ var m = minutes;
+ var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
+ var total = this.asSeconds();
+
+ if (!total) {
+ // this is the same as C#'s (Noda) and python (isodate)...
+ // but not other JS (goog.date)
+ return 'P0D';
+ }
+
+ var totalSign = total < 0 ? '-' : '';
+ var ymSign = sign(this._months) !== sign(total) ? '-' : '';
+ var daysSign = sign(this._days) !== sign(total) ? '-' : '';
+ var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
+
+ return totalSign + 'P' +
+ (Y ? ymSign + Y + 'Y' : '') +
+ (M ? ymSign + M + 'M' : '') +
+ (D ? daysSign + D + 'D' : '') +
+ ((h || m || s) ? 'T' : '') +
+ (h ? hmsSign + h + 'H' : '') +
+ (m ? hmsSign + m + 'M' : '') +
+ (s ? hmsSign + s + 'S' : '');
+}
+
+var proto$2 = Duration.prototype;
+
+proto$2.isValid = isValid$1;
+proto$2.abs = abs;
+proto$2.add = add$1;
+proto$2.subtract = subtract$1;
+proto$2.as = as;
+proto$2.asMilliseconds = asMilliseconds;
+proto$2.asSeconds = asSeconds;
+proto$2.asMinutes = asMinutes;
+proto$2.asHours = asHours;
+proto$2.asDays = asDays;
+proto$2.asWeeks = asWeeks;
+proto$2.asMonths = asMonths;
+proto$2.asYears = asYears;
+proto$2.valueOf = valueOf$1;
+proto$2._bubble = bubble;
+proto$2.clone = clone$1;
+proto$2.get = get$2;
+proto$2.milliseconds = milliseconds;
+proto$2.seconds = seconds;
+proto$2.minutes = minutes;
+proto$2.hours = hours;
+proto$2.days = days;
+proto$2.weeks = weeks;
+proto$2.months = months;
+proto$2.years = years;
+proto$2.humanize = humanize;
+proto$2.toISOString = toISOString$1;
+proto$2.toString = toISOString$1;
+proto$2.toJSON = toISOString$1;
+proto$2.locale = locale;
+proto$2.localeData = localeData;
+
+// Deprecations
+proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
+proto$2.lang = lang;
+
+// Side effect imports
+
+// FORMATTING
+
+addFormatToken('X', 0, 0, 'unix');
+addFormatToken('x', 0, 0, 'valueOf');
+
+// PARSING
+
+addRegexToken('x', matchSigned);
+addRegexToken('X', matchTimestamp);
+addParseToken('X', function (input, array, config) {
+ config._d = new Date(parseFloat(input, 10) * 1000);
+});
+addParseToken('x', function (input, array, config) {
+ config._d = new Date(toInt(input));
+});
+
+// Side effect imports
+
+
+hooks.version = '2.19.2';
+
+setHookCallback(createLocal);
+
+hooks.fn = proto;
+hooks.min = min;
+hooks.max = max;
+hooks.now = now;
+hooks.utc = createUTC;
+hooks.unix = createUnix;
+hooks.months = listMonths;
+hooks.isDate = isDate;
+hooks.locale = getSetGlobalLocale;
+hooks.invalid = createInvalid;
+hooks.duration = createDuration;
+hooks.isMoment = isMoment;
+hooks.weekdays = listWeekdays;
+hooks.parseZone = createInZone;
+hooks.localeData = getLocale;
+hooks.isDuration = isDuration;
+hooks.monthsShort = listMonthsShort;
+hooks.weekdaysMin = listWeekdaysMin;
+hooks.defineLocale = defineLocale;
+hooks.updateLocale = updateLocale;
+hooks.locales = listLocales;
+hooks.weekdaysShort = listWeekdaysShort;
+hooks.normalizeUnits = normalizeUnits;
+hooks.relativeTimeRounding = getSetRelativeTimeRounding;
+hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
+hooks.calendarFormat = getCalendarFormat;
+hooks.prototype = proto;
+
+return hooks;
+
+})));
diff --git a/basicsuite/ebike-ui/mostrecent.bson b/basicsuite/ebike-ui/mostrecent.bson
new file mode 100644
index 0000000..5e9edea
--- /dev/null
+++ b/basicsuite/ebike-ui/mostrecent.bson
Binary files differ
diff --git a/basicsuite/ebike-ui/navigation.cpp b/basicsuite/ebike-ui/navigation.cpp
new file mode 100644
index 0000000..067636e
--- /dev/null
+++ b/basicsuite/ebike-ui/navigation.cpp
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QJsonDocument>
+#include <QJsonArray>
+#include <QNetworkReply>
+
+#include "navigation.h"
+#include "mapbox.h"
+
+Navigation::Navigation(MapBox *mapbox, QObject *parent)
+ : QObject(parent)
+ , m_mapbox(mapbox)
+ , m_position(QGeoCoordinate(36.131961, -115.153048), QDateTime::currentDateTime())
+ , m_zoomlevel(18)
+ , m_active(false)
+ , m_routeDirection(0)
+ , m_routePosition(m_position.coordinate())
+{
+ m_position.setAttribute(QGeoPositionInfo::Direction, 0);
+}
+
+void Navigation::setPosition(const QGeoPositionInfo &position)
+{
+ if (m_position != position) {
+ m_position = position;
+ emit positionChanged(m_position);
+ }
+}
+
+void Navigation::setCoordinate(const QGeoCoordinate &coordinate)
+{
+ if (m_position.coordinate() != coordinate) {
+ m_position.setCoordinate(coordinate);
+ emit coordinateChanged(m_position.coordinate());
+ }
+}
+
+void Navigation::setDirection(qreal direction)
+{
+ if (qFuzzyCompare(m_position.attribute(QGeoPositionInfo::Direction), direction))
+ return;
+
+ m_position.setAttribute(QGeoPositionInfo::Direction, direction);
+ emit directionChanged(direction);
+}
+
+void Navigation::setZoomLevel(qreal zoomlevel)
+{
+ if (qFuzzyCompare(m_zoomlevel, zoomlevel))
+ return;
+
+ m_zoomlevel = zoomlevel;
+ emit zoomLevelChanged(m_zoomlevel);
+}
+
+void Navigation::setActive(bool active)
+{
+ if (m_active == active)
+ return;
+
+ m_active = active;
+ emit activeChanged(m_active);
+}
diff --git a/basicsuite/ebike-ui/navigation.h b/basicsuite/ebike-ui/navigation.h
new file mode 100644
index 0000000..6c9533f
--- /dev/null
+++ b/basicsuite/ebike-ui/navigation.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 NAVIGATION_H
+#define NAVIGATION_H
+
+#include <QObject>
+#include <QGeoPositionInfo>
+#include <QJsonObject>
+#include <QJSValue>
+#include <QTimer>
+
+#include <QDebug>
+
+class MapBox;
+
+class Navigation : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QGeoPositionInfo position READ position WRITE setPosition NOTIFY positionChanged)
+ Q_PROPERTY(QGeoCoordinate coordinate READ coordinate WRITE setCoordinate NOTIFY coordinateChanged)
+ Q_PROPERTY(qreal direction READ direction WRITE setDirection NOTIFY directionChanged)
+ Q_PROPERTY(qreal zoomlevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged)
+ Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
+ Q_PROPERTY(qreal routeDirection READ routeDirection NOTIFY routeDirectionChanged)
+ Q_PROPERTY(QGeoCoordinate routePosition READ routePosition NOTIFY routePositionChanged)
+
+public:
+ explicit Navigation(MapBox *mapbox, QObject *parent = nullptr);
+
+public:
+ // Getters
+ const QGeoPositionInfo &position() const { return m_position; }
+ QGeoCoordinate coordinate() const { return m_position.coordinate(); }
+ qreal direction() const { return m_position.attribute(QGeoPositionInfo::Direction); }
+ qreal zoomLevel() const { return m_zoomlevel; }
+ bool active() const { return m_active; }
+
+ qreal routeDirection() const { return m_routeDirection; }
+ QGeoCoordinate routePosition() const { return m_routePosition; }
+
+ // Setters
+ void setPosition(const QGeoPositionInfo &position);
+ void setCoordinate(const QGeoCoordinate &coordinate);
+ void setDirection(qreal direction);
+ void setZoomLevel(qreal zoomlevel);
+ void setActive(bool active);
+
+signals:
+ void positionChanged(QGeoPositionInfo position);
+ void coordinateChanged(QGeoCoordinate coordinate);
+ void directionChanged(qreal direction);
+ void zoomLevelChanged(qreal zoomlevel);
+ void activeChanged(bool active);
+
+ void routeDirectionChanged(qreal routeDirection);
+ void routePositionChanged(QGeoCoordinate routePosition);
+
+private:
+ MapBox *m_mapbox;
+ QGeoPositionInfo m_position;
+ qreal m_zoomlevel;
+ bool m_active;
+
+ qreal m_routeDirection;
+ QGeoCoordinate m_routePosition;
+};
+
+#endif // NAVIGATION_H
diff --git a/basicsuite/ebike-ui/preview_l.jpg b/basicsuite/ebike-ui/preview_l.jpg
new file mode 100644
index 0000000..4cd0851
--- /dev/null
+++ b/basicsuite/ebike-ui/preview_l.jpg
Binary files differ
diff --git a/basicsuite/ebike-ui/qml.qrc b/basicsuite/ebike-ui/qml.qrc
new file mode 100644
index 0000000..810c477
--- /dev/null
+++ b/basicsuite/ebike-ui/qml.qrc
@@ -0,0 +1,107 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qtquickcontrols2.conf</file>
+ <file>mainview.qml</file>
+ <file>NaviPage.qml</file>
+ <file>StatsPage.qml</file>
+ <file>MainPage.qml</file>
+ <file>BikeStyle/Colors.qml</file>
+ <file>BikeStyle/qmldir</file>
+ <file>SpeedView.qml</file>
+ <file>fonts/Montserrat-Medium.ttf</file>
+ <file>fonts/Montserrat-Bold.ttf</file>
+ <file>fonts/Montserrat-Light.ttf</file>
+ <file>fonts/Montserrat-Regular.ttf</file>
+ <file>StatsBox.qml</file>
+ <file>BikeStyle/UILayout.qml</file>
+ <file>NaviBox.qml</file>
+ <file>LightsBox.qml</file>
+ <file>ModeBox.qml</file>
+ <file>ClockView.qml</file>
+ <file>images/lights_off.png</file>
+ <file>images/lights_on.png</file>
+ <file>fonts/Teko-Bold.ttf</file>
+ <file>fonts/Teko-Light.ttf</file>
+ <file>fonts/Teko-Medium.ttf</file>
+ <file>fonts/Teko-Regular.ttf</file>
+ <file>MusicPlayer.qml</file>
+ <file>images/map-marker.png</file>
+ <file>images/trip.png</file>
+ <file>images/calories.png</file>
+ <file>images/nextsong.png</file>
+ <file>images/nextsong_pressed.png</file>
+ <file>images/play.png</file>
+ <file>images/play_pressed.png</file>
+ <file>images/prevsong.png</file>
+ <file>images/prevsong_pressed.png</file>
+ <file>images/speed.png</file>
+ <file>images/battery.png</file>
+ <file>images/assist.png</file>
+ <file>ConfigurationDrawer.qml</file>
+ <file>IconifiedTabButton.qml</file>
+ <file>fonts/fontawesome-webfont.ttf</file>
+ <file>GeneralTab.qml</file>
+ <file>ColumnSpacer.qml</file>
+ <file>BikeInfoTab.qml</file>
+ <file>TripChart.qml</file>
+ <file>images/top_curtain_drag.png</file>
+ <file>FpsItem.qml</file>
+ <file>images/spinner.png</file>
+ <file>images/checkmark.png</file>
+ <file>images/nav_left.png</file>
+ <file>images/nav_right.png</file>
+ <file>images/nav_straight.png</file>
+ <file>images/small_speedometer_arrow.png</file>
+ <file>images/map_locate.png</file>
+ <file>images/map_zoomin.png</file>
+ <file>images/map_zoomout.png</file>
+ <file>images/info.png</file>
+ <file>images/info_selected.png</file>
+ <file>images/list.png</file>
+ <file>images/list_selected.png</file>
+ <file>images/settings.png</file>
+ <file>images/settings_selected.png</file>
+ <file>ConfigurationItem.qml</file>
+ <file>images/curtain_up_arrow.png</file>
+ <file>NaviButton.qml</file>
+ <file>images/search.png</file>
+ <file>images/search_cancel.png</file>
+ <file>NaviGuide.qml</file>
+ <file>images/arrow_left.png</file>
+ <file>images/arrow_right.png</file>
+ <file>StatsRow.qml</file>
+ <file>images/fps_icon.png</file>
+ <file>images/curtain_shadow_handle.png</file>
+ <file>images/map_btn_shadow.png</file>
+ <file>images/map_destination.png</file>
+ <file>images/map_location_arrow.png</file>
+ <file>NaviTripInfo.qml</file>
+ <file>moment.js</file>
+ <file>images/small_speedometer_shadow.png</file>
+ <file>images/navigation_widget_shadow.png</file>
+ <file>images/small_input_box_shadow.png</file>
+ <file>images/nav_bear_l.png</file>
+ <file>images/nav_bear_r.png</file>
+ <file>images/nav_hard_l.png</file>
+ <file>images/nav_hard_r.png</file>
+ <file>images/nav_light_left.png</file>
+ <file>images/nav_light_right.png</file>
+ <file>images/nav_nodir.png</file>
+ <file>images/nav_uturn_l.png</file>
+ <file>images/nav_uturn_r.png</file>
+ <file>ViewTab.qml</file>
+ <file>images/pause.png</file>
+ <file>images/pause_pressed.png</file>
+ <file>images/ok.png</file>
+ <file>images/warning.png</file>
+ <file>images/bike-battery.png</file>
+ <file>images/bike-brakes.png</file>
+ <file>images/bike-chain.png</file>
+ <file>images/bike-frontwheel.png</file>
+ <file>images/bike-gears.png</file>
+ <file>images/bike-rearwheel.png</file>
+ <file>images/bike-light.png</file>
+ <file>ToggleSwitch.qml</file>
+ <file>images/blue_circle_gps_area.png</file>
+ </qresource>
+</RCC>
diff --git a/basicsuite/ebike-ui/qtquickcontrols2.conf b/basicsuite/ebike-ui/qtquickcontrols2.conf
new file mode 100644
index 0000000..1764b16
--- /dev/null
+++ b/basicsuite/ebike-ui/qtquickcontrols2.conf
@@ -0,0 +1,15 @@
+; This file can be edited to change the style of the application
+; See Styling Qt Quick Controls 2 in the documentation for details:
+; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html
+
+[Controls]
+Style=Default
+
+[Universal]
+Theme=Light
+;Accent=Steel
+
+[Material]
+Theme=Light
+;Accent=BlueGrey
+;Primary=BlueGray
diff --git a/basicsuite/ebike-ui/socketclient.cpp b/basicsuite/ebike-ui/socketclient.cpp
new file mode 100644
index 0000000..d40d268
--- /dev/null
+++ b/basicsuite/ebike-ui/socketclient.cpp
@@ -0,0 +1,152 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QDataStream>
+#include <QJsonDocument>
+
+#include "socketclient.h"
+
+SocketClient::SocketClient(QObject *parent)
+ : QObject(parent)
+ , m_socket(new QLocalSocket(this))
+{
+ // Connect socket signals
+ connect(m_socket, &QLocalSocket::connected, this, &SocketClient::connected);
+ connect(m_socket, &QLocalSocket::disconnected, this, &SocketClient::disconnected);
+ connect(m_socket, &QLocalSocket::readyRead, this, &SocketClient::readyRead);
+
+ // Setup timer to try to reconnect after disconnect
+ m_connectionTimer.setInterval(5000);
+ connect(&m_connectionTimer, &QTimer::timeout, this, &SocketClient::reconnect);
+ connect(m_socket, &QLocalSocket::connected, &m_connectionTimer, &QTimer::stop);
+ connect(m_socket, &QLocalSocket::disconnected,
+ &m_connectionTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
+ connect(m_socket, static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
+ &m_connectionTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
+}
+
+void SocketClient::connectToServer(const QString &servername)
+{
+ m_servername = servername;
+ reconnect();
+}
+
+void SocketClient::reconnect()
+{
+ qDebug("Connecting to server...");
+ m_socket->connectToServer(m_servername);
+}
+
+qint64 SocketClient::write(const QByteArray &data)
+{
+ return m_socket->write(data);
+}
+
+/**
+ * @brief send a QByteArray to the server
+ * @param message
+ *
+ * Adds the length of the message as a header and sends the message to the server.
+ */
+void SocketClient::sendToServer(const QByteArray &message)
+{
+ // Prepend the message length
+ QByteArray data;
+ QDataStream stream(&data, QIODevice::WriteOnly);
+ stream << static_cast<qint32>(message.size() + 4);
+ data.append(message);
+
+ write(data);
+}
+
+/**
+ * @brief send a Json object to the server
+ * @param message
+ *
+ * Sends a QJsonObject object to the server, encoded in Qt's internal binary format.
+ */
+void SocketClient::sendToServer(const QJsonObject &message)
+{
+ QJsonDocument doc(message);
+ sendToServer(doc.toBinaryData());
+}
+
+/**
+ * @brief reads incoming data from the server
+ *
+ * Parses only message headers and body as QByteArray, but does not care about
+ * the contents. All complete messages are processed at @see parseMessage.
+ */
+void SocketClient::readyRead()
+{
+ m_data += m_socket->readAll();
+
+ bool messagefound = true;
+ while (messagefound) {
+ messagefound = false;
+ // If we have at least some data
+ if (m_data.size() >= 4) {
+ // Extract message size
+ qint32 messagesize;
+ QDataStream stream(m_data.left(4));
+ stream >> messagesize;
+
+ // If we have enough data for at least one message
+ if (m_data.size() >= messagesize) {
+ // Extract actual message
+ QByteArray message = m_data.mid(4, messagesize - 4);
+ parseMessage(message);
+ // Drop necessary amount of bytes
+ m_data = m_data.mid(messagesize);
+ messagefound = true; // Try to parse another message
+ }
+ }
+ }
+}
+
+/**
+ * @brief parse the contents of a QByteArray
+ * @param message
+ *
+ * Contents are parsed from QJsonDocument's binary data. This separation allows
+ * the format to be changed later on, if need be.
+ */
+void SocketClient::parseMessage(const QByteArray &message)
+{
+ // Parse message from raw format
+ QJsonDocument doc = QJsonDocument::fromBinaryData(message);
+ emit newMessage(doc.object());
+}
diff --git a/basicsuite/ebike-ui/socketclient.h b/basicsuite/ebike-ui/socketclient.h
new file mode 100644
index 0000000..dc100a2
--- /dev/null
+++ b/basicsuite/ebike-ui/socketclient.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 SOCKETCLIENT_H
+#define SOCKETCLIENT_H
+
+#include <QObject>
+#include <QLocalSocket>
+#include <QTimer>
+#include <QJsonObject>
+
+/**
+ * @brief The SocketClient class
+ *
+ * Socket container and message parser for client communications.
+ */
+class SocketClient : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit SocketClient(QObject *parent = nullptr);
+
+public:
+ QLocalSocket* socket(void) const { return m_socket; }
+ qint64 write(const QByteArray &data);
+ void sendToServer(const QByteArray &message);
+ void sendToServer(const QJsonObject &message);
+
+private:
+ void parseMessage(const QByteArray &message);
+
+signals:
+ void connected();
+ void disconnected();
+
+ void newMessage(const QJsonObject &message);
+
+public slots:
+ void connectToServer(const QString &servername);
+
+private slots:
+ void reconnect();
+ void readyRead();
+
+private:
+ QLocalSocket *m_socket;
+ QString m_servername;
+ QTimer m_connectionTimer;
+ QByteArray m_data;
+};
+
+#endif // SOCKETCLIENT_H
diff --git a/basicsuite/ebike-ui/suggestionsmodel.cpp b/basicsuite/ebike-ui/suggestionsmodel.cpp
new file mode 100644
index 0000000..fa028f0
--- /dev/null
+++ b/basicsuite/ebike-ui/suggestionsmodel.cpp
@@ -0,0 +1,157 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 <QCoreApplication>
+#include <QDir>
+#include <QFile>
+#include <QJsonDocument>
+
+#include "suggestionsmodel.h"
+
+#define EBIKE_DEMO_MODE
+
+static const char mostRecentFilename[] = "mostrecent.bson";
+
+SuggestionsModel::SuggestionsModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ loadMostRecent();
+}
+
+SuggestionsModel::SuggestionsModel(const QJsonArray &suggestions, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_suggestions(suggestions)
+{
+ loadMostRecent();
+}
+
+QVariant SuggestionsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ // Only horizontal header
+ if (orientation == Qt::Vertical)
+ return QVariant();
+
+ if (role == Qt::DisplayRole && section == 0)
+ return tr("Place");
+
+ return QAbstractListModel::headerData(section, orientation, role);
+}
+
+int SuggestionsModel::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent)
+ return m_suggestions.isEmpty() ? m_mostrecent.size() : m_suggestions.size();
+}
+
+QVariant SuggestionsModel::data(const QModelIndex &index, int role) const
+{
+ QJsonObject obj = get(index.row());
+ if (role == Qt::DisplayRole) {
+ return obj.value("place_name").toVariant();
+ } else if (role == PlaceNameRole) {
+ return obj.value("place_name").toVariant();
+ }
+
+ return QVariant();
+}
+
+QHash<int, QByteArray> SuggestionsModel::roleNames() const
+{
+ QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
+ roles[PlaceNameRole] = "placename";
+ return roles;
+}
+
+const QJsonObject SuggestionsModel::get(int index) const
+{
+ return m_suggestions.isEmpty() ?
+ m_mostrecent[index].toObject() :
+ m_suggestions[index].toObject();
+}
+
+void SuggestionsModel::setSuggestions(const QJsonArray &suggestions)
+{
+ beginResetModel();
+ m_suggestions = suggestions;
+ endResetModel();
+ emit emptyChanged();
+}
+
+void SuggestionsModel::clear()
+{
+ beginResetModel();
+ m_suggestions = QJsonArray();
+ endResetModel();
+ emit emptyChanged();
+}
+
+void SuggestionsModel::addToMostRecent(const QJsonObject &place)
+{
+ Q_UNUSED(place)
+ // For the demo, do not add new most recent places
+#ifndef EBIKE_DEMO_MODE
+ if (!m_mostrecent.contains(place))
+ m_mostrecent.prepend(place);
+ if (m_mostrecent.size() > 3)
+ m_mostrecent.pop_back();
+ saveMostRecent();
+#endif
+}
+
+void SuggestionsModel::loadMostRecent()
+{
+ QDir dir(QCoreApplication::applicationDirPath());
+
+ // Load most recent places
+ QString mostRecentFilepath = dir.absoluteFilePath(mostRecentFilename);
+ QFile mostRecentFile(mostRecentFilepath);
+ if (mostRecentFile.open(QIODevice::ReadOnly)) {
+ QJsonDocument doc = QJsonDocument::fromBinaryData(mostRecentFile.readAll());
+ m_mostrecent = doc.array();
+ }
+}
+
+void SuggestionsModel::saveMostRecent() const
+{
+ QDir dir(QCoreApplication::applicationDirPath());
+
+ // Load most recent places
+ QString mostRecentFilepath = dir.absoluteFilePath(mostRecentFilename);
+ QFile mostRecentFile(mostRecentFilepath);
+ if (mostRecentFile.open(QIODevice::WriteOnly)) {
+ QJsonDocument doc(m_mostrecent);
+ mostRecentFile.write(doc.toBinaryData());
+ }
+}
diff --git a/basicsuite/ebike-ui/suggestionsmodel.h b/basicsuite/ebike-ui/suggestionsmodel.h
new file mode 100644
index 0000000..5bf22b5
--- /dev/null
+++ b/basicsuite/ebike-ui/suggestionsmodel.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 SUGGESTIONSMODEL_H
+#define SUGGESTIONSMODEL_H
+
+#include <QAbstractListModel>
+#include <QJsonArray>
+#include <QJsonObject>
+
+class SuggestionsModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(bool empty READ isEmpty RESET clear NOTIFY emptyChanged)
+
+public:
+ explicit SuggestionsModel(QObject *parent = nullptr);
+ explicit SuggestionsModel(const QJsonArray &suggestions, QObject *parent = nullptr);
+ enum SuggestionRoles {
+ PlaceNameRole = Qt::UserRole + 1
+ };
+
+public:
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ virtual int rowCount(const QModelIndex &parent) const;
+ virtual QVariant data(const QModelIndex &index, int role) const;
+ virtual QHash<int, QByteArray> roleNames() const;
+
+ Q_INVOKABLE const QJsonObject get(int index) const;
+ bool isEmpty() const { return m_suggestions.size() == 0; }
+
+ void setSuggestions(const QJsonArray &suggestions);
+ Q_INVOKABLE void clear();
+
+ Q_INVOKABLE void addToMostRecent(const QJsonObject &place);
+
+private:
+ void loadMostRecent();
+ void saveMostRecent() const;
+
+signals:
+ void emptyChanged();
+
+private:
+ QJsonArray m_mostrecent;
+ QJsonArray m_suggestions;
+};
+
+#endif // SUGGESTIONSMODEL_H
diff --git a/basicsuite/ebike-ui/tripdatamodel.cpp b/basicsuite/ebike-ui/tripdatamodel.cpp
new file mode 100644
index 0000000..b74605f
--- /dev/null
+++ b/basicsuite/ebike-ui/tripdatamodel.cpp
@@ -0,0 +1,135 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 "datastore.h"
+#include "tripdatamodel.h"
+
+#include <QDebug>
+
+TripDataModel::TripDataModel(DataStore *datastore, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_datastore(datastore)
+ , m_refreshing(false)
+ , m_saving(false)
+{
+}
+
+int TripDataModel::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent)
+ return m_trips.count() + 1; // +1 for current trip
+}
+
+QVariant TripDataModel::data(const QModelIndex &index, int role) const
+{
+ QJsonObject obj = get(index.row());
+ if (role == DurationRole)
+ return obj.value("duration").toDouble(); // Seconds
+ else if (role == DistanceRole)
+ return m_datastore->convertDistance(obj.value("distance").toDouble());
+ else if (role == CaloriesRole)
+ return obj.value("calories").toDouble();
+ else if (role == MaxspeedRole)
+ return m_datastore->convertSpeed(obj.value("maxspeed").toDouble());
+ else if (role == AvgspeedRole)
+ return m_datastore->convertSpeed(obj.value("distance").toDouble() / obj.value("duration").toDouble());
+ else if (role == AscentRole)
+ return obj.value("ascent").toDouble();
+ else if (role == StartTimeRole)
+ return obj.value("starttime").toDouble();
+
+ return QVariant();
+}
+
+QHash<int, QByteArray> TripDataModel::roleNames() const
+{
+ QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
+ roles[DurationRole] = "duration";
+ roles[DistanceRole] = "distance";
+ roles[CaloriesRole] = "calories";
+ roles[MaxspeedRole] = "maxspeed";
+ roles[AvgspeedRole] = "avgspeed";
+ roles[AscentRole] = "ascent";
+ roles[StartTimeRole] = "starttime";
+
+ return roles;
+}
+
+void TripDataModel::setTrips(const QJsonArray &trips)
+{
+ beginResetModel();
+ m_trips = trips;
+ endResetModel();
+ m_refreshing = false;
+ emit refreshingChanged(m_refreshing);
+ emit refreshed();
+}
+
+void TripDataModel::addTrip(const QJsonObject &trip)
+{
+ // Always append at the beginning of the list, easy to calculate
+ int newRow = 0;
+ beginInsertRows(QModelIndex(), newRow, newRow);
+ m_trips.append(trip);
+ endInsertRows();
+ emit tripDataSaved(newRow);
+}
+
+const QJsonObject TripDataModel::get(int index) const
+{
+ return index == 0 ? m_current : m_trips.at(m_trips.count() - index).toObject();
+}
+
+void TripDataModel::refresh()
+{
+ m_refreshing = true;
+ emit refreshingChanged(m_refreshing);
+ m_datastore->getTrips();
+}
+
+void TripDataModel::endTrip()
+{
+ m_saving = true;
+ emit savingChanged(m_saving);
+ m_datastore->endTrip();
+}
+
+void TripDataModel::setCurrentTrip(const QJsonObject &current)
+{
+ m_current = current;
+ QModelIndex currentIndex = index(0);
+ emit dataChanged(currentIndex, currentIndex);
+}
diff --git a/basicsuite/ebike-ui/tripdatamodel.h b/basicsuite/ebike-ui/tripdatamodel.h
new file mode 100644
index 0000000..56be84e
--- /dev/null
+++ b/basicsuite/ebike-ui/tripdatamodel.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure 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 TRIPDATAMODEL_H
+#define TRIPDATAMODEL_H
+
+#include <QAbstractListModel>
+#include <QJsonObject>
+#include <QJsonArray>
+
+class DataStore;
+
+class TripDataModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(bool refreshing READ refreshing NOTIFY refreshingChanged)
+ Q_PROPERTY(bool saving READ saving NOTIFY savingChanged)
+
+public:
+ explicit TripDataModel(DataStore *datastore, QObject *parent = nullptr);
+ enum TripDataRoles {
+ DurationRole = Qt::UserRole + 1,
+ DistanceRole,
+ CaloriesRole,
+ MaxspeedRole,
+ AvgspeedRole,
+ AscentRole,
+ StartTimeRole
+ };
+
+public:
+ virtual int rowCount(const QModelIndex &parent) const;
+ virtual QVariant data(const QModelIndex &index, int role) const;
+ virtual QHash<int, QByteArray> roleNames() const;
+
+ Q_INVOKABLE const QJsonObject get(int index) const;
+
+ void setTrips(const QJsonArray &trips);
+ void addTrip(const QJsonObject &trip);
+
+ bool refreshing() const { return m_refreshing; }
+ bool saving() const { return m_saving; }
+
+signals:
+ void refreshed();
+ void refreshingChanged(bool refreshing);
+ void savingChanged(bool saving);
+ void tripDataSaved(int index);
+
+public slots:
+ void refresh();
+ void endTrip();
+ void setCurrentTrip(const QJsonObject &current);
+
+private:
+ DataStore *m_datastore;
+ QJsonArray m_trips;
+ QJsonObject m_current;
+ bool m_refreshing;
+ bool m_saving;
+};
+
+#endif // TRIPDATAMODEL_H
diff --git a/basicsuite/enterprise-charts/BaseChart.qml b/basicsuite/enterprise-charts/BaseChart.qml
new file mode 100644
index 0000000..27811b0
--- /dev/null
+++ b/basicsuite/enterprise-charts/BaseChart.qml
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 QtCharts 2.0
+
+ChartView{
+ id: chart
+ titleFont.family: appFont
+ titleFont.styleName: "SemiBold"
+ titleColor: "white"
+ legend.alignment: Qt.AlignBottom
+ legend.font.family: appFont
+ legend.labelColor: "white"
+ antialiasing: true
+ backgroundColor: "transparent"
+}
diff --git a/basicsuite/enterprise-charts/View1.qml b/basicsuite/enterprise-charts/View1.qml
index 64aa78c..dad431b 100644
--- a/basicsuite/enterprise-charts/View1.qml
+++ b/basicsuite/enterprise-charts/View1.qml
@@ -53,31 +53,30 @@ import QtQuick 2.0
//![2]
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
property variant othersSlice: 0
+
//![1]
- ChartView {
+ BaseChart {
id: chart
title: "Top-5 car brand shares in Finland"
anchors.fill: parent
- legend.alignment: Qt.AlignBottom
- antialiasing: true
-
PieSeries {
id: pieSeries
- PieSlice { label: "Volkswagen"; value: 13.5 }
- PieSlice { label: "Toyota"; value: 10.9 }
- PieSlice { label: "Ford"; value: 8.6 }
- PieSlice { label: "Skoda"; value: 8.2 }
- PieSlice { label: "Volvo"; value: 6.8 }
+ PieSlice { label: "Volkswagen"; value: 13.5; color: _primaryGreen }
+ PieSlice { label: "Toyota"; value: 10.9; color: _mediumGreen }
+ PieSlice { label: "Ford"; value: 8.6; color: _darkGreen }
+ PieSlice { label: "Skoda"; value: 8.2; color: _primaryGrey }
+ PieSlice { label: "Volvo"; value: 6.8; color: _secondaryGrey }
}
}
Component.onCompleted: {
// You can also manipulate slices dynamically
othersSlice = pieSeries.append("Others", 52.0);
+ othersSlice.color = mediumGrey;
pieSeries.find("Volkswagen").exploded = true;
}
//![1]
diff --git a/basicsuite/enterprise-charts/View10.qml b/basicsuite/enterprise-charts/View10.qml
index adcd350..b61236f 100644
--- a/basicsuite/enterprise-charts/View10.qml
+++ b/basicsuite/enterprise-charts/View10.qml
@@ -51,21 +51,24 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Horizontal Stacked Bar series"
anchors.fill: parent
- legend.alignment: Qt.AlignBottom
- antialiasing: true
HorizontalStackedBarSeries {
axisY: BarCategoryAxis { categories: ["2007", "2008", "2009", "2010", "2011", "2012" ] }
- BarSet { label: "Bob"; values: [2, 2, 3, 4, 5, 6] }
- BarSet { label: "Susan"; values: [5, 1, 2, 4, 1, 7] }
- BarSet { label: "James"; values: [3, 5, 8, 13, 5, 8] }
+ BarSet { label: "Bob"; color: _primaryGreen; values: [2, 2, 3, 4, 5, 6] }
+ BarSet { label: "Susan"; color: _primaryGrey; values: [5, 1, 2, 4, 1, 7] }
+ BarSet { label: "James"; color: _secondaryGrey; values: [3, 5, 8, 13, 5, 8] }
+ }
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
}
}
//![1]
diff --git a/basicsuite/enterprise-charts/View11.qml b/basicsuite/enterprise-charts/View11.qml
index 37c3d80..319e94a 100644
--- a/basicsuite/enterprise-charts/View11.qml
+++ b/basicsuite/enterprise-charts/View11.qml
@@ -51,21 +51,25 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Horizontal Percent Bar series"
anchors.fill: parent
- legend.alignment: Qt.AlignBottom
- antialiasing: true
HorizontalPercentBarSeries {
axisY: BarCategoryAxis { categories: ["2007", "2008", "2009", "2010", "2011", "2012" ] }
- BarSet { label: "Bob"; values: [2, 2, 3, 4, 5, 6] }
- BarSet { label: "Susan"; values: [5, 1, 2, 4, 1, 7] }
- BarSet { label: "James"; values: [3, 5, 8, 13, 5, 8] }
+ BarSet { label: "Bob"; color: _primaryGreen; values: [2, 2, 3, 4, 5, 6] }
+ BarSet { label: "Susan"; color: _primaryGrey; values: [5, 1, 2, 4, 1, 7] }
+ BarSet { label: "James"; color: _secondaryGrey; values: [3, 5, 8, 13, 5, 8] }
+ }
+
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
}
}
//![1]
diff --git a/basicsuite/enterprise-charts/View12.qml b/basicsuite/enterprise-charts/View12.qml
index 39d8a13..bd40cbd 100644
--- a/basicsuite/enterprise-charts/View12.qml
+++ b/basicsuite/enterprise-charts/View12.qml
@@ -51,42 +51,39 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
id: chart
title: "Production costs"
- anchors.fill: parent
legend.visible: false
- antialiasing: true
-
+ anchors.fill: parent
PieSeries {
id: pieOuter
size: 0.96
holeSize: 0.7
- PieSlice { id: slice; label: "Alpha"; value: 19511; color: "#8AB846"; borderColor: "#163430" }
- PieSlice { label: "Epsilon"; value: 11105; color: "#C0EEFF"; borderColor: "#3B391C" }
- PieSlice { label: "Psi"; value: 9352; color: "#DF8939"; borderColor: "#13060C" }
+
+ PieSlice { id: slice; label: "Alpha"; value: 19511; color: _primaryGreen; borderColor: "#163430" }
+ PieSlice { label: "Epsilon"; value: 11105; color: _primaryGrey; borderColor: "#3B391C" }
+ PieSlice { label: "Psi"; value: 9352; color: darkGrey2; borderColor: "#13060C" }
}
PieSeries {
size: 0.7
id: pieInner
holeSize: 0.25
+ PieSlice { label: "Materials"; value: 10334; color: _mediumGreen; borderColor: "#163430" }
+ PieSlice { label: "Employee"; value: 3066; color: _darkGreen; borderColor: "#163430" }
+ PieSlice { label: "Logistics"; value: 6111; color: _mediumGreen; borderColor: "#163430" }
- PieSlice { label: "Materials"; value: 10334; color: "#8AB846"; borderColor: "#163430" }
- PieSlice { label: "Employee"; value: 3066; color: "#AAE356"; borderColor: "#163430" }
- PieSlice { label: "Logistics"; value: 6111; color: "#99CC4E"; borderColor: "#163430" }
-
- PieSlice { label: "Materials"; value: 7371; color: "#C0EEFF"; borderColor: "#3B391C" }
- PieSlice { label: "Employee"; value: 2443; color: "#C9FAFF"; borderColor: "#3B391C" }
- PieSlice { label: "Logistics"; value: 1291; color: "#B0FAFF"; borderColor: "#3B391C" }
+ PieSlice { label: "Materials"; value: 7371; color: mediumGrey2; borderColor: "#3B391C" }
+ PieSlice { label: "Employee"; value: 2443; color: mediumGrey; borderColor: "#3B391C" }
+ PieSlice { label: "Logistics"; value: 1291; color: mediumGrey2; borderColor: "#3B391C" }
- PieSlice { label: "Materials"; value: 4022; color: "#DF8939"; borderColor: "#13060C" }
- PieSlice { label: "Employee"; value: 3998; color: "#FC9D42"; borderColor: "#13060C" }
- PieSlice { label: "Logistics"; value: 1332; color: "#F2963F"; borderColor: "#13060C" }
+ PieSlice { label: "Materials"; value: 4022; color: _secondaryGrey; borderColor: "#13060C" }
+ PieSlice { label: "Employee"; value: 3998; color: darkGrey; borderColor: "#13060C" }
+ PieSlice { label: "Logistics"; value: 1332; color: _secondaryGrey; borderColor: "#13060C" }
}
}
@@ -96,11 +93,15 @@ Rectangle {
pieOuter.at(i).labelPosition = PieSlice.LabelOutside;
pieOuter.at(i).labelVisible = true;
pieOuter.at(i).borderWidth = 3;
+ pieOuter.at(i).labelColor = "white";
+ pieOuter.at(i).labelFont = appFont;
}
for (var i = 0; i < pieInner.count; i++) {
pieInner.at(i).labelPosition = PieSlice.LabelInsideNormal;
pieInner.at(i).labelVisible = true;
pieInner.at(i).borderWidth = 2;
+ pieInner.at(i).labelColor = "white";
+ pieInner.at(i).labelFont = appFont;
}
}
//![1]
diff --git a/basicsuite/enterprise-charts/View2.qml b/basicsuite/enterprise-charts/View2.qml
index 8f136cc..8f201a6 100644
--- a/basicsuite/enterprise-charts/View2.qml
+++ b/basicsuite/enterprise-charts/View2.qml
@@ -51,17 +51,15 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Line"
anchors.fill: parent
- antialiasing: true
-
LineSeries {
name: "LineSeries"
+ color: _primaryGreen
XYPoint { x: 0; y: 0 }
XYPoint { x: 1.1; y: 2.1 }
XYPoint { x: 1.9; y: 3.3 }
@@ -70,6 +68,13 @@ Rectangle {
XYPoint { x: 3.4; y: 3.0 }
XYPoint { x: 4.1; y: 3.3 }
}
+
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
+ }
}
//![1]
}
diff --git a/basicsuite/enterprise-charts/View3.qml b/basicsuite/enterprise-charts/View3.qml
index 8429050..3211e9e 100644
--- a/basicsuite/enterprise-charts/View3.qml
+++ b/basicsuite/enterprise-charts/View3.qml
@@ -51,17 +51,15 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Spline"
anchors.fill: parent
- antialiasing: true
-
SplineSeries {
name: "SplineSeries"
+ color: _primaryGreen
XYPoint { x: 0; y: 0.0 }
XYPoint { x: 1.1; y: 3.2 }
XYPoint { x: 1.9; y: 2.4 }
@@ -70,6 +68,13 @@ Rectangle {
XYPoint { x: 3.4; y: 2.3 }
XYPoint { x: 4.1; y: 3.1 }
}
+
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
+ }
}
//![1]
}
diff --git a/basicsuite/enterprise-charts/View4.qml b/basicsuite/enterprise-charts/View4.qml
index ac3ea56..442d231 100644
--- a/basicsuite/enterprise-charts/View4.qml
+++ b/basicsuite/enterprise-charts/View4.qml
@@ -51,15 +51,12 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "NHL All-Star Team Players"
anchors.fill: parent
- antialiasing: true
-
ValueAxis {
id: valueAxis
min: 2000
@@ -70,8 +67,8 @@ Rectangle {
AreaSeries {
name: "Russian"
- color: "#FFD52B1E"
- borderColor: "#FF0039A5"
+ color: mediumGrey2
+ borderColor: _darkGreen
borderWidth: 3
axisX: valueAxis
upperSeries: LineSeries {
@@ -93,8 +90,8 @@ Rectangle {
AreaSeries {
name: "Swedish"
- color: "#AF005292"
- borderColor: "#AFFDCA00"
+ color: lightGrey
+ borderColor: _mediumGreen
borderWidth: 3
axisX: valueAxis
upperSeries: LineSeries {
@@ -115,8 +112,8 @@ Rectangle {
AreaSeries {
name: "Finnish"
- color: "#00357F"
- borderColor: "#FEFEFE"
+ color: _secondaryGrey
+ borderColor: _primaryGreen
borderWidth: 3
axisX: valueAxis
upperSeries: LineSeries {
@@ -134,5 +131,12 @@ Rectangle {
XYPoint { x: 2011; y: 1 }
}
}
+
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
+ }
}
}
diff --git a/basicsuite/enterprise-charts/View5.qml b/basicsuite/enterprise-charts/View5.qml
index f1005c3..be7794b 100644
--- a/basicsuite/enterprise-charts/View5.qml
+++ b/basicsuite/enterprise-charts/View5.qml
@@ -51,18 +51,16 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Scatters"
anchors.fill: parent
- antialiasing: true
-
ScatterSeries {
id: scatter1
name: "Scatter1"
+ color: _primaryGreen
XYPoint { x: 1.5; y: 1.5 }
XYPoint { x: 1.5; y: 1.6 }
XYPoint { x: 1.57; y: 1.55 }
@@ -74,6 +72,7 @@ Rectangle {
ScatterSeries {
name: "Scatter2"
+ color: _primaryGrey
//![1]
XYPoint { x: 2.0; y: 2.0 }
XYPoint { x: 2.0; y: 2.1 }
@@ -82,5 +81,12 @@ Rectangle {
XYPoint { x: 2.4; y: 2.7 }
XYPoint { x: 2.67; y: 2.65 }
}
+
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
+ }
}
}
diff --git a/basicsuite/enterprise-charts/View6.qml b/basicsuite/enterprise-charts/View6.qml
index 6f29bf9..a104b8a 100644
--- a/basicsuite/enterprise-charts/View6.qml
+++ b/basicsuite/enterprise-charts/View6.qml
@@ -51,22 +51,25 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Bar series"
anchors.fill: parent
- legend.alignment: Qt.AlignBottom
- antialiasing: true
-
BarSeries {
id: mySeries
axisX: BarCategoryAxis { categories: ["2007", "2008", "2009", "2010", "2011", "2012" ] }
- BarSet { label: "Bob"; values: [2, 2, 3, 4, 5, 6] }
- BarSet { label: "Susan"; values: [5, 1, 2, 4, 1, 7] }
- BarSet { label: "James"; values: [3, 5, 8, 13, 5, 8] }
+ BarSet { label: "Bob"; color: _primaryGreen; values: [2, 2, 3, 4, 5, 6] }
+ BarSet { label: "Susan"; color: _primaryGrey; values: [5, 1, 2, 4, 1, 7] }
+ BarSet { label: "James"; color: _secondaryGrey; values: [3, 5, 8, 13, 5, 8] }
+ }
+
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
}
}
//![1]
diff --git a/basicsuite/enterprise-charts/View7.qml b/basicsuite/enterprise-charts/View7.qml
index c8b6654..78e5350 100644
--- a/basicsuite/enterprise-charts/View7.qml
+++ b/basicsuite/enterprise-charts/View7.qml
@@ -51,22 +51,24 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Stacked Bar series"
anchors.fill: parent
- legend.alignment: Qt.AlignBottom
- antialiasing: true
-
StackedBarSeries {
id: mySeries
axisX: BarCategoryAxis { categories: ["2007", "2008", "2009", "2010", "2011", "2012" ] }
- BarSet { label: "Bob"; values: [2, 2, 3, 4, 5, 6] }
- BarSet { label: "Susan"; values: [5, 1, 2, 4, 1, 7] }
- BarSet { label: "James"; values: [3, 5, 8, 13, 5, 8] }
+ BarSet { label: "Bob"; color: _primaryGreen; values: [2, 2, 3, 4, 5, 6] }
+ BarSet { label: "Susan"; color: _primaryGrey; values: [5, 1, 2, 4, 1, 7] }
+ BarSet { label: "James"; color: _secondaryGrey; values: [3, 5, 8, 13, 5, 8] }
+ }
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
}
}
//![1]
diff --git a/basicsuite/enterprise-charts/View8.qml b/basicsuite/enterprise-charts/View8.qml
index fbb30e3..803b6cd 100644
--- a/basicsuite/enterprise-charts/View8.qml
+++ b/basicsuite/enterprise-charts/View8.qml
@@ -51,21 +51,24 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Percent Bar series"
anchors.fill: parent
- legend.alignment: Qt.AlignBottom
- antialiasing: true
-
PercentBarSeries {
axisX: BarCategoryAxis { categories: ["2007", "2008", "2009", "2010", "2011", "2012" ] }
- BarSet { label: "Bob"; values: [2, 2, 3, 4, 5, 6] }
- BarSet { label: "Susan"; values: [5, 1, 2, 4, 1, 7] }
- BarSet { label: "James"; values: [3, 5, 8, 13, 5, 8] }
+ BarSet { label: "Bob"; color: _primaryGreen; values: [2, 2, 3, 4, 5, 6] }
+ BarSet { label: "Susan"; color: _primaryGrey; values: [5, 1, 2, 4, 1, 7] }
+ BarSet { label: "James"; color: _secondaryGrey; values: [3, 5, 8, 13, 5, 8] }
+ }
+
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
}
}
//![1]
diff --git a/basicsuite/enterprise-charts/View9.qml b/basicsuite/enterprise-charts/View9.qml
index 23bc818..a85711e 100644
--- a/basicsuite/enterprise-charts/View9.qml
+++ b/basicsuite/enterprise-charts/View9.qml
@@ -51,21 +51,24 @@
import QtQuick 2.0
import QtCharts 2.0
-Rectangle {
+Item {
anchors.fill: parent
-
//![1]
- ChartView {
+ BaseChart {
title: "Horizontal Bar series"
anchors.fill: parent
- legend.alignment: Qt.AlignBottom
- antialiasing: true
-
HorizontalBarSeries {
axisY: BarCategoryAxis { categories: ["2007", "2008", "2009", "2010", "2011", "2012" ] }
- BarSet { label: "Bob"; values: [2, 2, 3, 4, 5, 6] }
- BarSet { label: "Susan"; values: [5, 1, 2, 4, 1, 7] }
- BarSet { label: "James"; values: [3, 5, 8, 13, 5, 8] }
+ BarSet { label: "Bob"; color: _primaryGreen; values: [2, 2, 3, 4, 5, 6] }
+ BarSet { label: "Susan"; color: _primaryGrey; values: [5, 1, 2, 4, 1, 7] }
+ BarSet { label: "James"; color: _secondaryGrey; values: [3, 5, 8, 13, 5, 8] }
+ }
+
+ Component.onCompleted: {
+ axes[0].labelsColor = "white";
+ axes[1].labelsColor = "white";
+ axes[0].labelsFont = appFont;
+ axes[1].labelsFont = appFont;
}
}
//![1]
diff --git a/basicsuite/enterprise-charts/enterprise-charts.pro b/basicsuite/enterprise-charts/enterprise-charts.pro
index 7896c62..b71e6eb 100644
--- a/basicsuite/enterprise-charts/enterprise-charts.pro
+++ b/basicsuite/enterprise-charts/enterprise-charts.pro
@@ -4,6 +4,7 @@ include(../shared/shared.pri)
b2qtdemo_deploy_defaults()
content.files = \
+ BaseChart.qml \
loader.qml \
main.qml \
View1.qml \
@@ -17,7 +18,7 @@ content.files = \
View6.qml \
View7.qml \
View8.qml \
- View9.qml
+ View9.qml \
content.path = $$DESTPATH
diff --git a/basicsuite/enterprise-charts/main.qml b/basicsuite/enterprise-charts/main.qml
index d29ed2d..5fadbbd 100644
--- a/basicsuite/enterprise-charts/main.qml
+++ b/basicsuite/enterprise-charts/main.qml
@@ -56,6 +56,14 @@ Rectangle {
width: 600
height: 400
property bool sourceLoaded: false
+ color: _backgroundColor
+
+ property string darkGrey: "#222840"
+ property string darkGrey2: "#53586b"
+ property string mediumGrey: "#6b7080"
+ property string mediumGrey2: "#848895"
+ property string lightGrey: "#b5b7bf"
+
ListView {
id: root
@@ -112,6 +120,7 @@ Rectangle {
color: "white"
anchors.centerIn: parent
text: "You can navigate between views using swipe or arrow keys"
+ font.family: appFont
}
Behavior on opacity {
diff --git a/basicsuite/enterprise-charts/preview_l.jpg b/basicsuite/enterprise-charts/preview_l.jpg
index 131cc8f..a298283 100644
--- a/basicsuite/enterprise-charts/preview_l.jpg
+++ b/basicsuite/enterprise-charts/preview_l.jpg
Binary files differ
diff --git a/basicsuite/graphicaleffects/graphicaleffects.pro b/basicsuite/graphicaleffects/graphicaleffects.pro
index 2c51fe7..da9e8a3 100644
--- a/basicsuite/graphicaleffects/graphicaleffects.pro
+++ b/basicsuite/graphicaleffects/graphicaleffects.pro
@@ -17,7 +17,7 @@ content.files = \
effect_OpacityMask.qml \
effect_ThresholdMask.qml \
main.qml \
- images
+ images \
content.path = $$DESTPATH
diff --git a/basicsuite/graphicaleffects/main.qml b/basicsuite/graphicaleffects/main.qml
index 1b31a9d..4eb694d 100644
--- a/basicsuite/graphicaleffects/main.qml
+++ b/basicsuite/graphicaleffects/main.qml
@@ -73,7 +73,7 @@ Item {
anchors.right: checkers.left
anchors.top: parent.top
anchors.bottom: parent.bottom
- color: "black"
+ color: _backgroundColor
}
ListModel {
@@ -102,8 +102,6 @@ Item {
clip: true
focus: true
- highlightMoveDuration: 0
-
onCurrentItemChanged: {
var entry = listModel.get(currentIndex);
loader.source = entry.file;
@@ -111,9 +109,7 @@ Item {
model: listModel
- highlight: Rectangle {
- color: "steelblue"
- }
+
delegate: Item {
id: delegateRoot
@@ -121,20 +117,13 @@ Item {
width: list.width
height: root.height * 0.05
- Rectangle {
- width: parent.width
- height: 3
- anchors.bottom: parent.bottom
- gradient: Gradient {
- GradientStop { position: 0; color: "transparent" }
- GradientStop { position: 0.5; color: "lightgray" }
- GradientStop { position: 1; color: "transparent" }
- }
- }
+ property bool isSelected: list.currentIndex == index
Text {
- color: "white"
- font.pixelSize: parent.height * 0.5
+ color: parent.isSelected ? _primaryGreen : "white"
+ font.pixelSize: parent.height * 0.625
+ font.family: appFont
+ font.styleName: parent.isSelected ? "Bold" : "Regular"
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -2
x: parent.width * 0.1
@@ -162,14 +151,14 @@ Item {
var h = canvas.height;
- ctx.fillStyle = "rgb(50, 50, 50)"
+ ctx.fillStyle = "rgb(58, 64, 85)"
ctx.beginPath();
ctx.roundedRect(0, 0, w, h, w * 0.1, w * 0.1);
ctx.fill();
var margin = canvas.padding;
var segmentSize = 4
- ctx.strokeStyle = "gray"
+ ctx.strokeStyle = _primaryGrey
ctx.beginPath();
ctx.moveTo(margin, margin);
ctx.lineTo(margin, h-margin);
@@ -189,7 +178,7 @@ Item {
}
Rectangle {
- color: "red"
+ color: _primaryGreen
width: parent.width / 20
height: width
radius: width / 2
@@ -208,6 +197,7 @@ Item {
color: "white"
font.pixelSize: canvas.padding * 0.5
+ font.family: appFont
}
Text {
@@ -222,6 +212,7 @@ Item {
+ (loader.item != undefined && typeof loader.item.feedbackY != 'undefined' ? ": " + loader.item.feedbackY.toFixed(2) : "");
color: "white"
font.pixelSize: canvas.padding * 0.5
+ font.family: appFont
}
MouseArea {
diff --git a/basicsuite/graphicaleffects/preview_l.jpg b/basicsuite/graphicaleffects/preview_l.jpg
index fde1964..d5333c8 100644
--- a/basicsuite/graphicaleffects/preview_l.jpg
+++ b/basicsuite/graphicaleffects/preview_l.jpg
Binary files differ
diff --git a/basicsuite/mediaplayer/Content.qml b/basicsuite/mediaplayer/Content.qml
index aa31f09..00ad381 100755..100644
--- a/basicsuite/mediaplayer/Content.qml
+++ b/basicsuite/mediaplayer/Content.qml
@@ -85,6 +85,9 @@ Rectangle {
Loader {
id: effectLoader
source: effectSource
+ onItemChanged: {
+ updateSource()
+ }
}
onWidthChanged: {
@@ -99,11 +102,7 @@ Rectangle {
onEffectSourceChanged: {
effectLoader.source = effectSource
- effectLoader.item.parent = root
- effectLoader.item.targetWidth = root.width
- effectLoader.item.targetHeight = root.height
updateSource()
- effectLoader.item.source = theSource
}
function init() {
@@ -114,8 +113,13 @@ Rectangle {
function updateSource() {
theSource.sourceItem = videoContent.mediaSource == "" ? introBackground : videoContent
- if (effectLoader.item)
+ if (effectLoader.item) {
+ effectLoader.item.parent = root
+ effectLoader.item.targetWidth = root.width
+ effectLoader.item.targetHeight = root.height
effectLoader.item.anchors.fill = videoContent
+ effectLoader.item.source = theSource
+ }
}
function openVideo(path) {
diff --git a/basicsuite/mediaplayer/ContentVideo.qml b/basicsuite/mediaplayer/ContentVideo.qml
index 90cf21c..90cf21c 100755..100644
--- a/basicsuite/mediaplayer/ContentVideo.qml
+++ b/basicsuite/mediaplayer/ContentVideo.qml
diff --git a/basicsuite/mediaplayer/ControlBar.qml b/basicsuite/mediaplayer/ControlBar.qml
index 05d253e..cdf27f2 100755..100644
--- a/basicsuite/mediaplayer/ControlBar.qml
+++ b/basicsuite/mediaplayer/ControlBar.qml
@@ -53,8 +53,8 @@ import QtMultimedia 5.0
Rectangle {
id: controlBar
- height: 150
- color: "#88333333"
+ height: parent.height * 0.225
+ color: _backgroundColor
property MediaPlayer mediaPlayer: null
property bool isMouseAbove: false
@@ -73,13 +73,13 @@ Rectangle {
volumeControl.volume = mediaPlayer.volume;
}
-// MouseArea {
-// anchors.fill: controlBar
-// hoverEnabled: true
+ MouseArea {
+ anchors.fill: controlBar
+ hoverEnabled: true
-// onEntered: controlBar.isMouseAbove = true;
-// onExited: controlBar.isMouseAbove = false;
-// }
+ onEntered: controlBar.isMouseAbove = true;
+ onExited: controlBar.isMouseAbove = false;
+ }
function statusString(stat)
{
@@ -103,48 +103,12 @@ Rectangle {
return "UnknownStatus";
}
-// Text {
-// id: statusText
-// anchors.left: parent.left
-// anchors.bottom: parent.top
-// anchors.bottomMargin: 12
-// font.pixelSize: 18
-// color: "white"
-// text: "Status: " + statusString(mediaPlayer.status)
-// }
-
-// Text {
-// anchors.verticalCenter: statusText.verticalCenter
-// anchors.left: statusText.right
-// anchors.leftMargin: 16
-// font.pixelSize: 18
-// color: "white"
-// text: Math.round(mediaPlayer.bufferProgress * 100.0) + "%"
-// }
-
- VolumeControl {
- id: volumeControl
- anchors.verticalCenter: playbackControl.verticalCenter
- anchors.left: controlBar.left
- anchors.leftMargin: 15
- onVolumeChanged: mediaPlayer.volume = volume
-
- Component.onCompleted: {
- volumeControl.volume = 0.5;
- }
-
- Connections {
- target: mediaPlayer
- onVolumeChanged: volumeControl.volume = mediaPlayer.volume
- }
- }
-
//Playback Controls
PlaybackControl {
id: playbackControl
- anchors.horizontalCenter: controlBar.horizontalCenter
- anchors.bottom: seekControl.top
- anchors.bottomMargin: 20
+ anchors.left: controlBar.left
+ anchors.top: controlBar.top
+ anchors.bottom: controlBar.bottom
onPlayButtonPressed: {
if (isPlaying) {
@@ -171,14 +135,37 @@ Rectangle {
onStopButtonPressed: mediaPlayer.stop();
}
+ //Seek controls
+ SeekControl {
+ id: seekControl
+ anchors.bottom: controlBar.bottom
+ anchors.bottomMargin: controlBar.height * 0.1
+ anchors.right: volumeControl.left
+ anchors.left: playbackControl.right
+ anchors.rightMargin: 15
+ anchors.leftMargin: 15
+ enabled: playbackControl.isPlaybackEnabled
+
+ duration: mediaPlayer.duration
+
+ onSeekValueChanged: {
+ mediaPlayer.seek(newPosition);
+ position = mediaPlayer.position;
+ }
+
+ Component.onCompleted: {
+ seekable = mediaPlayer.seekable;
+ }
+ }
+
//Toolbar Controls
Row {
id: toolbarMenuButtons
- anchors.right: controlBar.right
+ anchors.right: volumeControl.left
anchors.rightMargin: 15
- anchors.verticalCenter: playbackControl.verticalCenter
- spacing: 22
-
+ anchors.bottom: seekControl.top
+ spacing: itemMargin
+ height: parent.height * 0.275
ImageButton {
id: fxButton
imageSource: "images/FXButton.png"
@@ -191,6 +178,7 @@ Rectangle {
ImageButton {
id: fileButton
imageSource: "images/FileButton.png"
+
onClicked: {
openFile();
}
@@ -198,47 +186,29 @@ Rectangle {
ImageButton {
id: urlButton
imageSource: "images/UrlButton.png"
+
onClicked: {
openURL();
}
}
- }
-
-// ImageButton {
-// id: fullscreenButton
-// imageSource: "images/FullscreenButton.png"
-// onClicked: {
-// //Toggle fullscreen
-// toggleFullScreen();
-// }
-// checkable: true
-// checked: applicationWindow.isFullScreen
-// anchors.right: controlBar.right
-// anchors.top: controlBar.top
-// anchors.rightMargin: 15
-// anchors.topMargin: 15
-// }
+ ImageButton{
+ id: infoButton
- //Seek controls
- SeekControl {
- id: seekControl
- anchors.bottom: controlBar.bottom
- anchors.bottomMargin: 10
- anchors.right: controlBar.right
- anchors.left: controlBar.left
- anchors.rightMargin: 15
- anchors.leftMargin: 15
- enabled: playbackControl.isPlaybackEnabled
-
- duration: mediaPlayer.duration
-
- onSeekValueChanged: {
- mediaPlayer.seek(newPosition);
- position = mediaPlayer.position;
+ imageSource: "images/info_icon.svg"
+ onClicked: metadataView.opacity = 1
+ visible: content.videoPlayer.mediaPlayer.status !== MediaPlayer.NoMedia && content.videoPlayer.mediaPlayer.status !== MediaPlayer.InvalidMedia
}
+ }
- Component.onCompleted: {
- seekable = mediaPlayer.seekable;
+ VolumeControl {
+ id: volumeControl
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ onVolumeChanged: mediaPlayer.volume = volume
+
+ Connections {
+ target: mediaPlayer
+ onVolumeChanged: volumeControl.volume = mediaPlayer.volume
}
}
@@ -264,7 +234,6 @@ Rectangle {
}
onSeekableChanged: {
- // console.log("seekableChanged: " + mediaPlayer.seekable);
seekControl.seekable = mediaPlayer.seekable;
}
}
diff --git a/basicsuite/mediaplayer/EffectSelectionPanel.qml b/basicsuite/mediaplayer/EffectSelectionPanel.qml
index 4f29f50..9aee910 100755..100644
--- a/basicsuite/mediaplayer/EffectSelectionPanel.qml
+++ b/basicsuite/mediaplayer/EffectSelectionPanel.qml
@@ -51,8 +51,7 @@ import QtQuick 2.0
Rectangle {
id: root
- color: "#BB333333"
- height: 78
+ color: _backgroundColor
property int itemHeight: 25
property string effectSource: ""
property bool isMouseAbove: mouseAboveMonitor.containsMouse
@@ -76,14 +75,14 @@ Rectangle {
ListElement { name: "Glow"; source: "Effects/EffectGlow.qml" }
ListElement { name: "Isolate"; source: "Effects/EffectIsolate.qml" }
//ListElement { name: "Magnify"; source: "Effects/EffectMagnify.qml" }
-// ListElement { name: "Page curl"; source: "Effects/EffectPageCurl.qml" }
+ // ListElement { name: "Page curl"; source: "Effects/EffectPageCurl.qml" }
ListElement { name: "Pixelate"; source: "Effects/EffectPixelate.qml" }
ListElement { name: "Posterize"; source: "Effects/EffectPosterize.qml" }
-// ListElement { name: "Ripple"; source: "Effects/EffectRipple.qml" }
+ // ListElement { name: "Ripple"; source: "Effects/EffectRipple.qml" }
ListElement { name: "Sepia"; source: "Effects/EffectSepia.qml" }
ListElement { name: "Sharpen"; source: "Effects/EffectSharpen.qml" }
ListElement { name: "Shockwave"; source: "Effects/EffectShockwave.qml" }
-// ListElement { name: "Tilt shift"; source: "Effects/EffectTiltShift.qml" }
+ // ListElement { name: "Tilt shift"; source: "Effects/EffectTiltShift.qml" }
ListElement { name: "Toon"; source: "Effects/EffectToon.qml" }
ListElement { name: "Warhol"; source: "Effects/EffectWarhol.qml" }
ListElement { name: "Wobble"; source: "Effects/EffectWobble.qml" }
@@ -104,6 +103,7 @@ Rectangle {
height: 0.8 * itemHeight
text: name
color: "white"
+ font.family: appFont
}
states: [
@@ -140,7 +140,7 @@ Rectangle {
id: list
anchors.fill: parent
clip: true
- anchors.margins: 14
+ anchors.margins: itemMargin
model: sources
focus: root.visible && root.opacity && urlBar.opacity === 0
@@ -153,18 +153,20 @@ Rectangle {
}
delegate: Item {
- height: 40
+ height: itemHeight
width: parent.width
- Rectangle {
+ property bool isSelected: list.currentIndex == index
+ Text {
+ color: parent.isSelected ? _primaryGreen : "white"
+ text: name
+ anchors.centerIn: parent
+ font.pixelSize: defaultFontSize
+ font.family: appFont
+ font.styleName: parent.isSelected ? "Bold" : "Regular"
+ }
+ MouseArea {
anchors.fill: parent
- border.color: index == list.currentIndex ? "#44ffffff" : "transparent"
- color: index == list.currentIndex ? "#22ffffff" : "transparent"
- radius: 3
- Text { color: "white" ; text: name ; anchors.centerIn: parent; font.pixelSize: 20 }
- MouseArea {
- anchors.fill: parent
- onClicked: list.currentIndex = index
- }
+ onClicked: list.currentIndex = index
}
}
}
diff --git a/basicsuite/mediaplayer/FileBrowser.qml b/basicsuite/mediaplayer/FileBrowser.qml
index 5ffe130..a111177 100644
--- a/basicsuite/mediaplayer/FileBrowser.qml
+++ b/basicsuite/mediaplayer/FileBrowser.qml
@@ -50,11 +50,12 @@
import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
import QtQuick.Controls 1.4
+import QtDeviceUtilities.QtButtonImageProvider 1.0
Item {
id: fileBrowser
- property string folder: "file:///data/videos"
+ property string folder: VideosLocation
signal fileSelected(string file)
@@ -77,7 +78,7 @@ Item {
Rectangle {
id: root
- color: "white"
+ color: _backgroundColor
property alias folder: folders.folder
FolderListModel {
@@ -110,7 +111,8 @@ Item {
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: fileName
font.pixelSize: parent.height * .1
- color: view.currentIndex === index ? "#80c342" : "#46484a"
+ font.family: appFont
+ color: mouseRegion.pressed ? _primaryGreen : "white"
elide: Text.ElideRight
}
@@ -182,16 +184,17 @@ Item {
Rectangle {
width: parent.width;
height: 70
- color: "#f6f6f6"
+ color: _backgroundColor
id: titleBar
- Button {
+ QtButton {
id: backButton
text: qsTr("Back")
anchors.left: parent.left
anchors.leftMargin: parent.width * .05
anchors.verticalCenter: parent.verticalCenter
onClicked: up()
+ fillColor: _primaryGreen
}
Text {
@@ -203,28 +206,28 @@ Item {
anchors.bottom: parent.bottom
text: String(folders.folder).replace("file://", "")
- color: "#46484a"
+ color: "white"
elide: Text.ElideLeft
verticalAlignment: Text.AlignVCenter
font.pixelSize: backButton.height * .8
+ font.family: appFont
}
- Button {
+ QtButton {
id: cancelButton
text: qsTr("Cancel")
- checkable: true
- checked: true
anchors.right: parent.right
anchors.rightMargin: parent.width * .05
anchors.verticalCenter: parent.verticalCenter
onClicked: fileBrowser.selectFile("")
+ fillColor: _primaryGreen
}
Rectangle {
width: parent.width
anchors.bottom: parent.bottom
height: 2
- color: "#e4e4e4"
+ color: _primaryGrey
}
}
diff --git a/basicsuite/mediaplayer/ImageButton.qml b/basicsuite/mediaplayer/ImageButton.qml
index 31bd315..66888ef 100755..100644
--- a/basicsuite/mediaplayer/ImageButton.qml
+++ b/basicsuite/mediaplayer/ImageButton.qml
@@ -49,6 +49,7 @@
****************************************************************************/
import QtQuick 2.0
+import QtGraphicalEffects 1.0
Item {
id: root
@@ -73,15 +74,17 @@ Item {
visible: true
opacity: pressed ? 0.6 : 1
smooth: true
+ height: toolbarMenuButtons.height
+ width: height
}
-// ColorOverlay {
-// id: glowEffect
-// anchors.fill: image
-// source: image
-// color: pressed ? "#22000000" : checked ? "orange" : "white"
-// visible: checked || hover || pressed
-// }
+ ColorOverlay {
+ id: glowEffect
+ anchors.fill: image
+ source: image
+ color: pressed ? "#22000000" : checked ? _primaryGreen : "white"
+ visible: checked || hover || pressed
+ }
MouseArea {
id: mouseArea
diff --git a/basicsuite/mediaplayer/MetadataView.qml b/basicsuite/mediaplayer/MetadataView.qml
index e85df5e..34b53b4 100644
--- a/basicsuite/mediaplayer/MetadataView.qml
+++ b/basicsuite/mediaplayer/MetadataView.qml
@@ -48,6 +48,7 @@
**
****************************************************************************/
import QtQuick 2.0
+import QtMultimedia 5.0
Rectangle {
id: root
@@ -62,10 +63,12 @@ Rectangle {
Rectangle {
height: column.height + 30
width: 500
- color: "#BB222222"
+ color: _backgroundColor
+ opacity: 0.9
anchors.centerIn: parent
anchors.verticalCenterOffset: -50
-
+ border.color: _primaryGrey
+ border.width: 2
Column {
id: column
@@ -80,6 +83,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.mediaType !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -88,6 +92,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.title !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -96,6 +101,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.leadPerformer !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -104,6 +110,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.contributingArtist !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -112,6 +119,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.albumTitle !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -120,6 +128,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.albumArtist !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -128,6 +137,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.author !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -136,6 +146,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.composer !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -144,6 +155,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.writer !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -152,6 +164,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.genre !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -160,6 +173,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.trackNumber !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -168,6 +182,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.year !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -176,6 +191,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.duration !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -184,6 +200,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.resolution !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -192,6 +209,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.audioBitRate !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -200,6 +218,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.videoBitRate !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
@@ -208,6 +227,7 @@ Rectangle {
visible: mediaPlayer && mediaPlayer.metaData.date !== undefined
color: "white"
font.pixelSize: 24
+ font.family: appFont
width: parent.width
wrapMode: Text.WordWrap
}
diff --git a/basicsuite/mediaplayer/ParameterPanel.qml b/basicsuite/mediaplayer/ParameterPanel.qml
index bc6a73e..b67381a 100644
--- a/basicsuite/mediaplayer/ParameterPanel.qml
+++ b/basicsuite/mediaplayer/ParameterPanel.qml
@@ -51,11 +51,10 @@ import QtQuick 2.0
Rectangle {
id: root
- height: view.model.count * sliderHeight
- color: "#BB333333"
+ color: _backgroundColor
property color lineColor: "black"
property real spacing: 10
- property real sliderHeight: 50
+ property real sliderHeight: height * 0.4
property bool isMouseAbove: mouseAboveMonitor.containsMouse
property ListModel model: ListModel { }
@@ -82,24 +81,26 @@ Rectangle {
anchors {
top: parent.top
bottom: parent.bottom
- leftMargin: 15
+ leftMargin: itemMargin
left: parent.left
}
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
- font.pixelSize: 20
+ font.pixelSize: defaultFontSize
font.capitalization: Font.Capitalize
- width: 90
+ font.family: appFont
+ width: root.width * 0.2
+ fontSizeMode: Text.Fit
}
- Slider {
+ PlayerSlider {
anchors {
verticalCenter: text.verticalCenter
verticalCenterOffset: 3
left: text.right
- leftMargin: 20
+ leftMargin: itemMargin
right: parent.right
- rightMargin: 20
+ rightMargin: itemMargin
}
value: model.value
onValueChanged: view.model.setProperty(index, "value", value)
diff --git a/basicsuite/mediaplayer/PlaybackControl.qml b/basicsuite/mediaplayer/PlaybackControl.qml
index 66c1c92..99b1d5c 100755..100644
--- a/basicsuite/mediaplayer/PlaybackControl.qml
+++ b/basicsuite/mediaplayer/PlaybackControl.qml
@@ -49,11 +49,13 @@
****************************************************************************/
import QtQuick 2.0
+import QtDeviceUtilities.QtButtonImageProvider 1.0
Row {
+ anchors.leftMargin: parent.width * 0.02
id: root
- spacing: 26
- height: playButton.height
+ spacing: parent.width * 0.02
+ height: parent.height
property bool isPlaybackEnabled: false
property bool isPlaying: false
@@ -64,46 +66,42 @@ Row {
signal stopButtonPressed()
//Playback Controls
- ImageButton {
- id: rateReverseButton
- enabled: isPlaybackEnabled
- imageSource: "images/RateButtonReverse.png"
- anchors.verticalCenter: root.verticalCenter
- onClicked: {
- reverseButtonPressed();
+ QtButton{
+ id: playPauseButton
+ width: controlBar.width * 0.06
+ height: width
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: root.spacing
+ fillColor: _primaryGreen
+ Image{
+ anchors.centerIn: parent
+ width: parent.width * 0.5
+ height: width
+ source: !isPlaying ? "images/play_icon.svg" : "images/pause_icon.svg"
+ sourceSize.width: parent.width
+ sourceSize.height: parent.height
}
- }
- ImageButton {
- id: playButton
- enabled: isPlaybackEnabled
- imageSource: !isPlaying ? "images/PlayButton.png" : "images/PauseButton.png"
- anchors.verticalCenter: root.verticalCenter
-// anchors.right: rateForwardButton.left
-// anchors.rightMargin: 10
onClicked: {
playButtonPressed();
}
}
-// Rectangle{
-// enabled: isPlaybackEnabled
-// color: "white"
-// opacity: enabled ? 1 : 0.3
-// width: playButton.width
-// height: width
-// anchors.verticalCenter: root.verticalCenter
-// MouseArea {
-// anchors.fill: parent
-// onClicked: stopButtonPressed();
-// }
-// }
-
- ImageButton {
- id: rateForwardButton
- enabled: isPlaybackEnabled
- imageSource: "images/RateButtonForward.png"
- anchors.verticalCenter: root.verticalCenter
+ QtButton{
+ id: stopButton
+ width: controlBar.width * 0.06
+ height: width
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: root.spacing
+ fillColor: _primaryGreen
+ Image{
+ anchors.centerIn: parent
+ width: parent.width * 0.5
+ height: width
+ source: "images/stop_icon.svg"
+ sourceSize.width: parent.width
+ sourceSize.height: parent.height
+ }
onClicked: {
- forwardButtonPressed();
+ stopButtonPressed();
}
}
}
diff --git a/basicsuite/mediaplayer/Slider.qml b/basicsuite/mediaplayer/PlayerSlider.qml
index 0cacbc7..0ec52b5 100644
--- a/basicsuite/mediaplayer/Slider.qml
+++ b/basicsuite/mediaplayer/PlayerSlider.qml
@@ -51,8 +51,7 @@ import QtQuick 2.0
Item {
id: slider
-
- height: handleBack.height
+ height: handle.height
// value is read/write.
property real value: 0
property real maximum: 1
@@ -81,8 +80,9 @@ Item {
id: background
width: slider.width
anchors.verticalCenter: slider.verticalCenter
- height: 2
- color: "#666666"
+ height: 5
+ color: _primaryGrey
+ radius: 2
MouseArea {
id: backgroundMouse
@@ -119,29 +119,19 @@ Item {
anchors.right: handle.right
anchors.rightMargin: handle.width / 2
visible: slider.enabled
- color: "#98c66c"
+ color: "white"
+ radius: 2
}
Rectangle {
- id: handleBack
+ id: handle
width: 40
height: width
radius: width / 2
- color: "#8898c66c"
+ color: _primaryGreen
antialiasing: true
anchors.centerIn: handle
- visible: handle.visible
- }
-
- Rectangle {
- id: handle
- width: 14
- height: width
- radius: width / 2
- antialiasing: true
- color: "#98c66c"
- anchors.verticalCenter: background.verticalCenter
- visible: slider.enabled
+ visible: true
}
}
diff --git a/basicsuite/mediaplayer/SeekControl.qml b/basicsuite/mediaplayer/SeekControl.qml
index a7ca93f..16987f7 100755..100644
--- a/basicsuite/mediaplayer/SeekControl.qml
+++ b/basicsuite/mediaplayer/SeekControl.qml
@@ -48,11 +48,11 @@
**
****************************************************************************/
import QtQuick 2.0
+import QtQuick.Controls 2.2
Item {
id: root
- height: seekSlider.height
-
+ height: parent.height * 0.5
property int position: 0
property int duration: 0
property bool seekable: false
@@ -62,29 +62,15 @@ Item {
signal seekValueChanged(int newPosition)
onPositionChanged: {
- elapsedText.text = formatTime(position);
seekSlider.value = position;
}
- onDurationChanged: {
- remainingText.text = formatTime(duration);
- }
-
- Text {
- id: elapsedText
- anchors.verticalCenter: seekSlider.verticalCenter
- anchors.left: root.left
- text: "00:00"
- font.pixelSize: 20
- color: "#cccccc"
- }
-
- Slider {
+ PlayerSlider {
id: seekSlider
- anchors.leftMargin: 30
- anchors.rightMargin: 30
- anchors.left: elapsedText.right
- anchors.right: remainingText.left
+ anchors.leftMargin: parent.width * 0.02
+ anchors.rightMargin: parent.width * 0.02
+ anchors.left: parent.left
+ anchors.right: parent.right
anchors.verticalCenter: root.verticalCenter
mutable: root.seekable
enabled: root.enabled
@@ -98,15 +84,6 @@ Item {
}
}
- Text {
- id: remainingText
- anchors.verticalCenter: seekSlider.verticalCenter
- anchors.right: root.right
- text: "00:00"
- font.pixelSize: 20
- color: "#cccccc"
- }
-
function formatTime(time) {
time = time / 1000
var hours = Math.floor(time / 3600);
diff --git a/basicsuite/mediaplayer/UrlBar.qml b/basicsuite/mediaplayer/UrlBar.qml
index dbaa8d0..73e9cc5 100644
--- a/basicsuite/mediaplayer/UrlBar.qml
+++ b/basicsuite/mediaplayer/UrlBar.qml
@@ -48,6 +48,7 @@
**
****************************************************************************/
import QtQuick 2.0
+import QtQuick.Controls 2.0
Rectangle {
id: root
@@ -76,54 +77,39 @@ Rectangle {
text: "Enter URL"
color: "white"
font.pixelSize: 20
+ font.family: appFont
}
- BorderImage {
- id: urlBar
- source: "images/ControlBar.png"
- border.top: 12
- border.bottom: 12
- border.left: 12
- border.right: 12
+ TextField {
+ id: urlInput
height: 70
- anchors.centerIn: parent
- anchors.verticalCenterOffset: -170
width: 600
+ anchors.horizontalCenter: parent.horizontalCenter
+ font.pixelSize: height * 0.45
+ color: "white"
+ text: ""
+ placeholderText: "http://"
+ font.family: appFont
+ font.styleName: "Light"
+ background: Rectangle {
+ color: _backgroundColor
+ opacity: 0.8
+ border.color: _primaryGrey
+ border.width: 2
+ }
+ onAccepted: root.urlAccepted(urlInput.text);
+ }
- Rectangle {
+ Rectangle {
+ anchors.right: urlBar.left
+ anchors.rightMargin: 32
+ anchors.verticalCenter: urlBar.verticalCenter
+ height: 70
+ width: 70
+ color: _primaryGrey
+ MouseArea {
anchors.fill: parent
- anchors.margins: 16
- color: "#66ffffff"
- border.color: "#bbffffff"
- radius: 2
- antialiasing: true
-
- TextInput {
- id: urlInput
- selectionColor: "#aaffffff"
- selectedTextColor: "black"
- selectByMouse: true
- anchors.fill: parent
- anchors.margins: 5
- font.pixelSize: 24
- color: "black"
- text: "http://"
- onAccepted: root.urlAccepted(urlInput.text);
-
- }
+ onClicked: { urlInput.text = ""; urlInput.paste(); }
}
}
-
-// Rectangle {
-// anchors.right: urlBar.left
-// anchors.rightMargin: 32
-// anchors.verticalCenter: urlBar.verticalCenter
-// height: 70
-// width: 70
-// color: "gray"
-// MouseArea {
-// anchors.fill: parent
-// onClicked: { urlInput.text = ""; urlInput.paste(); }
-// }
-// }
}
diff --git a/basicsuite/mediaplayer/VolumeControl.qml b/basicsuite/mediaplayer/VolumeControl.qml
index f2bd427..ab8bec8 100755..100644
--- a/basicsuite/mediaplayer/VolumeControl.qml
+++ b/basicsuite/mediaplayer/VolumeControl.qml
@@ -49,46 +49,110 @@
****************************************************************************/
import QtQuick 2.0
+import QtDeviceUtilities.QtButtonImageProvider 1.0
+import QtQuick.Controls 2.2
Item {
id: root
- width: 210
- height: volumeUp.height
-
+ width: parent.width * 0.1
+ height: parent.height
property alias volume: volumeSlider.value
-
+ property bool muted: root.volume == 0.0
+ property bool volumeItemUp: volumeItem.y == (volumeItem.height + volumeItem.height * 0.05)
//Volume Controls
- ImageButton {
- id: volumeDown
- imageSource: "images/VolumeDown.png"
- anchors.verticalCenter: root.verticalCenter
- anchors.left: root.left
- scale: 1.4
+ QtButton{
+ id: toggleVolumeButton
+ width: controlBar.width * 0.06
+ height: width
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: controlBar.width * 0.02
+ anchors.horizontalCenter: parent.horizontalCenter
+ fillColor: _primaryGreen
+
+ Image{
+ anchors.centerIn: parent
+ width: parent.width * 0.5
+ height: width
+ source: !muted ? "images/volume_icon.svg" : "images/mute_icon.svg"
+ sourceSize.width: parent.width
+ sourceSize.height: parent.height
+ }
onClicked: {
- root.volume = 0.0;
+ if (volumeItem.opacity == 0)
+ volumeItem.opacity = 1;
+ else if (volumeItem.opacity > 0)
+ volumeItem.opacity = 0;
+
+ if (volumeItem.y === 0)
+ volumeItem.y -= (volumeItem.height + itemMargin);
+ else if (volumeItem !== 0)
+ volumeItem.y = 0;
}
}
- Slider {
- id: volumeSlider
- anchors.left: volumeDown.right
- anchors.leftMargin: 22
- anchors.rightMargin: 22
- anchors.right: volumeUp.left
- maximum: 1.0
- minimum: 0.0
- anchors.verticalCenter: root.verticalCenter
- anchors.verticalCenterOffset: 1
- }
- ImageButton {
- id: volumeUp
- imageSource: "images/VolumeUp.png"
- anchors.verticalCenter: root.verticalCenter
- anchors.verticalCenterOffset: 1
- anchors.right: root.right
- scale: 1.4
- onClicked: {
- root.volume = 1.0
+ Item{
+ id: volumeItem
+ height: applicationWindow.height * 0.4
+ anchors.right: toggleVolumeButton.left //?
+ opacity: 0
+ y: 0
+ z: -1000
+ QtButton{
+ id: muteVolumeButton
+ width: controlBar.width * 0.06
+ height: width
+ anchors.bottom: parent.bottom
+ fillColor: _primaryGrey
+ borderColor: "transparent"
+ Image{
+ anchors.centerIn: parent
+ width: parent.width * 0.5
+ height: parent.height * 0.5
+ source: "images/mute_icon.svg"
+ sourceSize.width: parent.width
+ sourceSize.height: parent.height
+ }
+ onClicked: {
+ root.volume = 0.0
+ }
}
+
+ Slider {
+ id: volumeSlider
+ anchors.bottom: muteVolumeButton.top
+ anchors.bottomMargin: itemMargin
+ anchors.top: parent.top
+ anchors.horizontalCenter: muteVolumeButton.horizontalCenter
+ orientation: Qt.Vertical
+
+ background: Rectangle{
+ id: sliderBackground
+ x: volumeSlider.leftPadding + volumeSlider.availableWidth / 2 - width / 2
+ y: volumeSlider.bottomPadding
+ implicitWidth: 5
+ implicitHeight: 200
+ height: volumeSlider.availableHeight
+ width: implicitWidth
+ radius: 2
+ color: "white"
+ Rectangle{
+ height: volumeSlider.visualPosition * parent.height
+ width: parent.width
+ color: _primaryGrey
+ radius: 2
+ }
+ }
+ handle: Rectangle{
+ x: volumeSlider.leftPadding + volumeSlider.availableWidth / 2 - width / 2
+ y: volumeSlider.bottomPadding + volumeSlider.visualPosition * (volumeSlider.availableHeight - height)
+ width: sliderBackground.width * 7
+ height: width
+ radius: height * 0.5
+ color: _primaryGreen
+ }
+ }
+
+ Behavior on opacity { NumberAnimation { duration: 100 } }
+ Behavior on y { NumberAnimation { duration: 100 } }
}
}
diff --git a/basicsuite/mediaplayer/images/FullscreenButton.png b/basicsuite/mediaplayer/images/FullscreenButton.png
index 413872a..413872a 100755..100644
--- a/basicsuite/mediaplayer/images/FullscreenButton.png
+++ b/basicsuite/mediaplayer/images/FullscreenButton.png
Binary files differ
diff --git a/basicsuite/mediaplayer/images/PlaybackSlider.png b/basicsuite/mediaplayer/images/PlaybackSlider.png
index 3716315..3716315 100755..100644
--- a/basicsuite/mediaplayer/images/PlaybackSlider.png
+++ b/basicsuite/mediaplayer/images/PlaybackSlider.png
Binary files differ
diff --git a/basicsuite/mediaplayer/images/SliderHandle.png b/basicsuite/mediaplayer/images/SliderHandle.png
index 5206100..5206100 100755..100644
--- a/basicsuite/mediaplayer/images/SliderHandle.png
+++ b/basicsuite/mediaplayer/images/SliderHandle.png
Binary files differ
diff --git a/basicsuite/mediaplayer/images/SliderProgress.png b/basicsuite/mediaplayer/images/SliderProgress.png
index e0efc87..e0efc87 100755..100644
--- a/basicsuite/mediaplayer/images/SliderProgress.png
+++ b/basicsuite/mediaplayer/images/SliderProgress.png
Binary files differ
diff --git a/basicsuite/mediaplayer/images/VolumeDown.png b/basicsuite/mediaplayer/images/VolumeDown.png
index 60c626d..60c626d 100755..100644
--- a/basicsuite/mediaplayer/images/VolumeDown.png
+++ b/basicsuite/mediaplayer/images/VolumeDown.png
Binary files differ
diff --git a/basicsuite/mediaplayer/images/VolumeUp.png b/basicsuite/mediaplayer/images/VolumeUp.png
index 886fde7..886fde7 100755..100644
--- a/basicsuite/mediaplayer/images/VolumeUp.png
+++ b/basicsuite/mediaplayer/images/VolumeUp.png
Binary files differ
diff --git a/basicsuite/mediaplayer/images/back_icon.svg b/basicsuite/mediaplayer/images/back_icon.svg
new file mode 100644
index 0000000..6034bd7
--- /dev/null
+++ b/basicsuite/mediaplayer/images/back_icon.svg
@@ -0,0 +1 @@
+<svg id="back" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>back_icon</title><path d="M26.57,43.5a2.61,2.61,0,0,1-2.1-1.06L10.74,24a2.62,2.62,0,0,1,0-3.12L25.06,1.56a2.62,2.62,0,1,1,4.2,3.12L16.1,22.4,28.67,39.32a2.62,2.62,0,0,1-2.1,4.18Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/bluetooth_icon.svg b/basicsuite/mediaplayer/images/bluetooth_icon.svg
new file mode 100644
index 0000000..5dfaa97
--- /dev/null
+++ b/basicsuite/mediaplayer/images/bluetooth_icon.svg
@@ -0,0 +1 @@
+<svg id="bluetooth" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>bluetooth_icon</title><path d="M22.38,43.5a1.79,1.79,0,0,1-1.79-1.79V25.82l-9.08,7.86A1.79,1.79,0,1,1,9.18,31l10.37-9L9.1,13a1.79,1.79,0,0,1,2.34-2.7l9.16,7.92V2.29a1.79,1.79,0,0,1,3-1.35l11.34,9.81a1.79,1.79,0,0,1,0,2.7L25,22l9.88,8.55a1.79,1.79,0,0,1,0,2.7L23.55,43.06A1.79,1.79,0,0,1,22.38,43.5ZM24.17,26V37.81L31,31.9Zm0-19.8V18L31,12.1Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/close_icon.svg b/basicsuite/mediaplayer/images/close_icon.svg
new file mode 100644
index 0000000..c98fe51
--- /dev/null
+++ b/basicsuite/mediaplayer/images/close_icon.svg
@@ -0,0 +1 @@
+<svg id="close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>close_icon</title><path d="M25.5,22,42.09,4.78a2.52,2.52,0,1,0-3.64-3.5L22,18.36,5.55,1.27a2.53,2.53,0,0,0-3.64,3.5L18.49,22,1.91,39.22a2.53,2.53,0,0,0,3.64,3.5L22,25.64,38.45,42.73a2.52,2.52,0,1,0,3.64-3.5Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/header_toggle_icon.svg b/basicsuite/mediaplayer/images/header_toggle_icon.svg
new file mode 100644
index 0000000..06d0b6a
--- /dev/null
+++ b/basicsuite/mediaplayer/images/header_toggle_icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>header_toggle_icon</title><g style="isolation:isolate"><g id="toggle"><g style="opacity:0.35000000000000003;mix-blend-mode:multiply"><polygon points="3.36 15 23.27 33 42.64 15 3.36 15" style="fill:none;stroke:#09102b;stroke-linejoin:round;stroke-width:2px"/></g><polygon points="2.36 13 22.27 31 41.64 13 2.36 13" style="fill:none;stroke:#fff;stroke-linejoin:round;stroke-width:2px"/></g></g></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/info_icon.svg b/basicsuite/mediaplayer/images/info_icon.svg
new file mode 100644
index 0000000..64d24c0
--- /dev/null
+++ b/basicsuite/mediaplayer/images/info_icon.svg
@@ -0,0 +1 @@
+<svg id="info" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>info_icon</title><path d="M21.86,15.59a2,2,0,0,1-1.33-.48A1.69,1.69,0,0,1,20,13.77a1.68,1.68,0,0,1,.56-1.3A1.91,1.91,0,0,1,21.86,12a1.93,1.93,0,0,1,1.28.46,1.65,1.65,0,0,1,.56,1.35,1.7,1.7,0,0,1-.54,1.34,1.88,1.88,0,0,1-1.29.48m1.83,3.83V30.14a2.39,2.39,0,0,1-.53,1.68,1.76,1.76,0,0,1-1.35.57,1.67,1.67,0,0,1-1.32-.58A2.47,2.47,0,0,1,20,30.14V19.53a2.38,2.38,0,0,1,.51-1.66,1.71,1.71,0,0,1,1.32-.56,1.78,1.78,0,0,1,1.35.56,2.16,2.16,0,0,1,.53,1.55" style="fill:#fff"/><path d="M21.89,43.5A21.5,21.5,0,0,1,6.7,6.8,21.5,21.5,0,0,1,37.11,37.2a21.38,21.38,0,0,1-15.21,6.3m0-39.35A17.85,17.85,0,0,0,9.28,34.62,17.85,17.85,0,1,0,34.52,9.38,17.73,17.73,0,0,0,21.9,4.15" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/language_icon.svg b/basicsuite/mediaplayer/images/language_icon.svg
new file mode 100644
index 0000000..f1235c5
--- /dev/null
+++ b/basicsuite/mediaplayer/images/language_icon.svg
@@ -0,0 +1 @@
+<svg id="language" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>language_icon</title><path d="M22,43.5A21.5,21.5,0,1,1,43.5,22,21.52,21.52,0,0,1,22,43.5ZM22,4.12A17.88,17.88,0,1,0,39.88,22,17.9,17.9,0,0,0,22,4.12Z" style="fill:#fff"/><path d="M22,42.6C15.22,42.6,9.7,33.36,9.7,22S15.22,1.41,22,1.41,34.29,10.64,34.29,22,28.78,42.6,22,42.6ZM22,3.22c-5.78,0-10.48,8.43-10.48,18.78S16.22,40.78,22,40.78,32.48,32.36,32.48,22,27.78,3.22,22,3.22Z" style="fill:#fff"/><path d="M22,42.6a.9.9,0,0,1-.91-.91V2.31A.9.9,0,0,1,22,1.41,20.59,20.59,0,0,1,22,42.6ZM22.9,3.24V40.76a18.78,18.78,0,0,0,0-37.53Z" style="fill:#fff"/><path d="M40.41,29.85H3.59a.91.91,0,0,1-.85-.59,20.37,20.37,0,0,1,0-14.52.91.91,0,0,1,.85-.59H40.41a.91.91,0,0,1,.85.59,20.34,20.34,0,0,1,0,14.52.91.91,0,0,1-.85.59ZM4.23,28H39.77a18.45,18.45,0,0,0,0-12.07H4.23A18.49,18.49,0,0,0,4.23,28Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/mute_icon.svg b/basicsuite/mediaplayer/images/mute_icon.svg
new file mode 100644
index 0000000..2ffd61f
--- /dev/null
+++ b/basicsuite/mediaplayer/images/mute_icon.svg
@@ -0,0 +1 @@
+<svg id="mute" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>mute_icon</title><path d="M17.92,40.48A2,2,0,0,1,16.68,40L7.59,32.77H2a2,2,0,0,1-2-2V13.22a2,2,0,0,1,2-2h5.6L16.68,4A2,2,0,0,1,19.9,5.5v33a2,2,0,0,1-2,2ZM4,28.8H8.29a2,2,0,0,1,1.24.44l6.41,5.13V9.64L9.52,14.77a2,2,0,0,1-1.24.44H4V28.8Z" style="fill:#fff"/><path d="M42,32.41a2,2,0,0,1-1.4-.58L23.77,15a2,2,0,0,1,2.81-2.81L43.42,29A2,2,0,0,1,42,32.41Z" style="fill:#fff"/><path d="M25.29,32.52a2,2,0,0,1-1.42-3.37L40.48,12.08a2,2,0,0,1,2.85,2.77L26.71,31.92a2,2,0,0,1-1.42.6Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/pause_icon.svg b/basicsuite/mediaplayer/images/pause_icon.svg
new file mode 100644
index 0000000..04000f9
--- /dev/null
+++ b/basicsuite/mediaplayer/images/pause_icon.svg
@@ -0,0 +1 @@
+<svg id="pause" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>pause_icon</title><path d="M15.51,43.5H9.76a4.65,4.65,0,0,1-4.64-4.64V5.14A4.65,4.65,0,0,1,9.76.5h5.75a4.65,4.65,0,0,1,4.64,4.64V38.86a4.65,4.65,0,0,1-4.64,4.64ZM9.76,4.55a.6.6,0,0,0-.59.59V38.86a.6.6,0,0,0,.59.59h5.75a.6.6,0,0,0,.59-.59V5.14a.6.6,0,0,0-.59-.59Z" style="fill:#fff"/><path d="M34.24,43.5H28.49a4.65,4.65,0,0,1-4.64-4.64V5.14A4.65,4.65,0,0,1,28.49.5h5.75a4.65,4.65,0,0,1,4.64,4.64V38.86a4.65,4.65,0,0,1-4.64,4.64ZM28.49,4.55a.6.6,0,0,0-.59.59V38.86a.6.6,0,0,0,.59.59h5.75a.6.6,0,0,0,.59-.59V5.14a.6.6,0,0,0-.59-.59Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/play_icon.svg b/basicsuite/mediaplayer/images/play_icon.svg
new file mode 100644
index 0000000..ab27771
--- /dev/null
+++ b/basicsuite/mediaplayer/images/play_icon.svg
@@ -0,0 +1 @@
+<svg id="play" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>play_icon</title><path d="M6.58,43.82c-2.4,0-4.08-1.91-4.08-4.65V4.83C2.5,2.09,4.18.18,6.58.18A5.29,5.29,0,0,1,9.22.95L39,18.12A4.56,4.56,0,0,1,41.5,22,4.56,4.56,0,0,1,39,25.88L9.22,43.05a5.28,5.28,0,0,1-2.65.77ZM6.64,4.28a5.24,5.24,0,0,0,0,.55V39.17a1.56,1.56,0,0,0,.08.55,1.74,1.74,0,0,0,.51-.21L36.92,22.33a1.61,1.61,0,0,0,.46-.36c-.06,0-.19-.16-.46-.31L7.18,4.49a1.6,1.6,0,0,0-.54-.22Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/power_icon.svg b/basicsuite/mediaplayer/images/power_icon.svg
new file mode 100644
index 0000000..b922c0e
--- /dev/null
+++ b/basicsuite/mediaplayer/images/power_icon.svg
@@ -0,0 +1 @@
+<svg id="power" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>power_icon</title><path d="M22,17.4a1.82,1.82,0,0,1-1.82-1.82V2.3a1.82,1.82,0,0,1,3.65,0V15.57A1.82,1.82,0,0,1,22,17.4Z" style="fill:#fff"/><path d="M22,43.48A19.15,19.15,0,0,1,15.7,6.25,1.82,1.82,0,0,1,16.9,9.7a15.51,15.51,0,1,0,10.19,0,1.82,1.82,0,0,1,1.2-3.45A19.15,19.15,0,0,1,22,43.48Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/qt-logo.png b/basicsuite/mediaplayer/images/qt-logo.png
index f3e8f29..f3e8f29 100755..100644
--- a/basicsuite/mediaplayer/images/qt-logo.png
+++ b/basicsuite/mediaplayer/images/qt-logo.png
Binary files differ
diff --git a/basicsuite/mediaplayer/images/settings_icon.svg b/basicsuite/mediaplayer/images/settings_icon.svg
new file mode 100644
index 0000000..7f5dbde
--- /dev/null
+++ b/basicsuite/mediaplayer/images/settings_icon.svg
@@ -0,0 +1 @@
+<svg id="settings" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>settings_icon</title><path d="M41,17.82H35a14.73,14.73,0,0,0-1.47-3.24l4-4a2.38,2.38,0,0,0,0-3.38L35.83,5.53a2.39,2.39,0,0,0-3.38,0l-4,4a14.34,14.34,0,0,0-3.53-1.43V2.89A2.39,2.39,0,0,0,22.49.5H20.1a2.39,2.39,0,0,0-2.39,2.39V8.14a14,14,0,0,0-3.84,1.61L10.49,6.37a2.4,2.4,0,0,0-3.38,0L5.42,8.06a2.4,2.4,0,0,0,0,3.38l3.44,3.44A14.24,14.24,0,0,0,7.28,19H2.79A2.39,2.39,0,0,0,.39,21.4v2.39a2.39,2.39,0,0,0,2.39,2.38h4.8a14,14,0,0,0,1.93,4L6.27,33.4a2.4,2.4,0,0,0,0,3.38L8,38.47a2.4,2.4,0,0,0,3.38,0L15,34.84a13.89,13.89,0,0,0,3.94,1.27v5A2.4,2.4,0,0,0,21.3,43.5h2.39a2.4,2.4,0,0,0,2.39-2.39V35.5a14,14,0,0,0,3.38-1.72l3.85,3.85a2.4,2.4,0,0,0,3.38,0l1.69-1.69a2.38,2.38,0,0,0,0-3.38l-4.21-4.22A14.15,14.15,0,0,0,35.31,25H41a2.39,2.39,0,0,0,2.39-2.39V20.21A2.4,2.4,0,0,0,41,17.82ZM21.3,31.5A9.5,9.5,0,1,1,30.8,22,9.5,9.5,0,0,1,21.3,31.5Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/settingsmenu_launcher_icon.svg b/basicsuite/mediaplayer/images/settingsmenu_launcher_icon.svg
new file mode 100644
index 0000000..479a79f
--- /dev/null
+++ b/basicsuite/mediaplayer/images/settingsmenu_launcher_icon.svg
@@ -0,0 +1 @@
+<svg id="settings_menu_launcher" data-name="settings menu launcher" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>settingsmenu_launcher_icon</title><rect y="1.86" width="44" height="9.43" style="fill:#9d9faa"/><rect y="17.57" width="44" height="9.43" style="fill:#9d9faa"/><rect y="33.29" width="44" height="7.86" style="fill:#9d9faa"/><rect y="1.86" width="44" height="9.43" style="fill:#9ca0aa"/><rect y="17.57" width="44" height="9.43" style="fill:#9ca0aa"/><rect y="33.29" width="44" height="7.86" style="fill:#9ca0aa"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/stop_icon.svg b/basicsuite/mediaplayer/images/stop_icon.svg
new file mode 100644
index 0000000..2eeff5f
--- /dev/null
+++ b/basicsuite/mediaplayer/images/stop_icon.svg
@@ -0,0 +1 @@
+<svg id="stop" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>stop_icon</title><path d="M37.66,44H6.34A6.35,6.35,0,0,1,0,37.66V6.34A6.35,6.35,0,0,1,6.34,0H37.66A6.35,6.35,0,0,1,44,6.34V37.66A6.35,6.35,0,0,1,37.66,44ZM6.34,4.14a2.2,2.2,0,0,0-2.2,2.2V37.66a2.2,2.2,0,0,0,2.2,2.2H37.66a2.2,2.2,0,0,0,2.2-2.2V6.34a2.2,2.2,0,0,0-2.2-2.2Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/images/titlebar.sci b/basicsuite/mediaplayer/images/titlebar.sci
index 0418d94..0418d94 100755..100644
--- a/basicsuite/mediaplayer/images/titlebar.sci
+++ b/basicsuite/mediaplayer/images/titlebar.sci
diff --git a/basicsuite/mediaplayer/images/volume_icon.svg b/basicsuite/mediaplayer/images/volume_icon.svg
new file mode 100644
index 0000000..12f944e
--- /dev/null
+++ b/basicsuite/mediaplayer/images/volume_icon.svg
@@ -0,0 +1 @@
+<svg id="volume" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><title>volume_icon</title><path d="M33.13,40.13a2,2,0,0,1-1.4-3.39,20.87,20.87,0,0,0,0-29.48,2,2,0,1,1,2.81-2.81,24.85,24.85,0,0,1,0,35.09,2,2,0,0,1-1.4.58Z" style="fill:#fff"/><path d="M27.38,34.38A2,2,0,0,1,26,31a12.72,12.72,0,0,0,0-18,2,2,0,0,1,2.81-2.81,16.69,16.69,0,0,1,0,23.59,2,2,0,0,1-1.4.58Z" style="fill:#fff"/><path d="M20.13,40.48A2,2,0,0,1,18.89,40L9.8,32.77H4.19a2,2,0,0,1-2-2V13.22a2,2,0,0,1,2-2h5.6L18.89,4A2,2,0,0,1,22.12,5.5v33a2,2,0,0,1-2,2ZM6.18,28.8h4.32a2,2,0,0,1,1.24.44l6.41,5.13V9.64l-6.42,5.14a2,2,0,0,1-1.24.44H6.18V28.8Z" style="fill:#fff"/></svg> \ No newline at end of file
diff --git a/basicsuite/mediaplayer/main.qml b/basicsuite/mediaplayer/main.qml
index d1667bf..741309d 100755..100644
--- a/basicsuite/mediaplayer/main.qml
+++ b/basicsuite/mediaplayer/main.qml
@@ -50,11 +50,16 @@
import QtQuick 2.0
import QtMultimedia 5.0
+import QtDeviceUtilities.QtButtonImageProvider 1.0
FocusScope {
id: applicationWindow
focus: true
+ property real buttonHeight: height * 0.05
+ property real itemMargin: Math.min(width * 0.025, height * 0.025)
+ property real defaultFontSize: height * 0.0375
+
MouseArea {
id: mouseActivityMonitor
anchors.fill: parent
@@ -114,42 +119,57 @@ FocusScope {
onOpenFX: {
applicationWindow.openFX();
}
-
- onToggleFullScreen: {
-// viewer.toggleFullscreen();
- }
}
- ParameterPanel {
- id: parameterPanel
- opacity: controlBar.opacity
- visible: effectSelectionPanel.visible && model.count !== 0
- height: 116
- width: 500
+ Rectangle {
+ id: effectsWrapper
+ color: "transparent"
+ Behavior on opacity { NumberAnimation { } }
anchors {
- bottomMargin: 15
+ top: parent.top
bottom: controlBar.top
- right: effectSelectionPanel.left
- rightMargin: 15
+ bottomMargin: itemMargin
+ right: parent.right
+ left: parent.left
}
- }
+ visible: opacity > 0
+ opacity: 0
- EffectSelectionPanel {
- id: effectSelectionPanel
- visible: false
- opacity: controlBar.opacity
- anchors {
- bottom: controlBar.top
- right: controlBar.right
- // rightMargin: 15
- bottomMargin: 15
+ ParameterPanel {
+ id: parameterPanel
+ opacity: controlBar.opacity * 0.9
+ visible: effectSelectionPanel.visible && model.count !== 0
+ height: parent.height * 0.15
+ width: parent.width * 0.4
+ anchors {
+ bottom: parent.bottom
+ right: effectSelectionPanel.left
+ rightMargin: itemMargin
+ }
+ z: 10
}
- width: 250
- height: 350
- itemHeight: 80
- onEffectSourceChanged: {
- content.effectSource = effectSource
- parameterPanel.model = content.effect.parameters
+
+ EffectSelectionPanel {
+ id: effectSelectionPanel
+ opacity: controlBar.opacity * 0.9
+ width: parent.width * 0.225
+ height: parent.height * 0.7
+ anchors {
+ right: parent.right
+ bottom: parent.bottom
+ }
+ z: 10
+ onEffectSourceChanged: {
+ content.effectSource = effectSource
+ parameterPanel.model = content.effect.parameters
+ }
+ itemHeight: height / 8
+ }
+
+ MouseArea{
+ anchors.fill: parent
+ onClicked: effectsWrapper.opacity = 0
+ enabled: effectsWrapper.opacity !== 0
}
}
@@ -165,35 +185,6 @@ FocusScope {
}
}
- Rectangle {
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.rightMargin: 32
- anchors.topMargin: 32
- width: 40
- height: 40
- radius: width / 2
- color: infoMouser.pressed ? "#BB999999" : "#88444444"
- antialiasing: true
- visible: content.videoPlayer.mediaPlayer.status !== MediaPlayer.NoMedia && controlBar.state === "VISIBLE"
-
- Text {
- anchors.centerIn: parent
- text: "i"
- font.italic: true
- font.bold: true
- color: "white"
- font.pixelSize: 28
- }
-
- MouseArea {
- id: infoMouser
- anchors.fill: parent
- anchors.margins: -10
- onClicked: metadataView.opacity = 1
- }
- }
-
MetadataView {
id: metadataView
mediaPlayer: content.videoPlayer.mediaPlayer
@@ -223,7 +214,6 @@ FocusScope {
openFX();
return;
} else if (event.key === Qt.Key_F && event.modifiers & Qt.ControlModifier) {
-// viewer.toggleFullscreen();
return;
} else if (event.key === Qt.Key_Up || event.key === Qt.Key_VolumeUp) {
content.videoPlayer.mediaPlayer.volume = Math.min(1, content.videoPlayer.mediaPlayer.volume + 0.1);
@@ -241,7 +231,6 @@ FocusScope {
}
return;
} else if (applicationWindow.isFullScreen && event.key === Qt.Key_Escape) {
-// viewer.toggleFullscreen();
return;
}
@@ -267,14 +256,10 @@ FocusScope {
function init() {
content.init()
- content.openVideo("file:///data/videos/Qt_video_720p.webm");
+ content.openVideo(DefaultVideoUrl);
}
function openVideo() {
- //videoFileBrowser.show()
- // var videoFile = viewer.openFileDialog();
- // if (videoFile != "")
- // content.openVideo(videoFile);
fileBrowser.show()
}
@@ -287,7 +272,7 @@ FocusScope {
}
function openFX() {
- effectSelectionPanel.visible = !effectSelectionPanel.visible;
+ effectsWrapper.opacity = effectsWrapper.opacity === 0 ? 1 : 0
}
function close() {
diff --git a/basicsuite/mediaplayer/mediaplayer.pro b/basicsuite/mediaplayer/mediaplayer.pro
index 4667caf..c56ba88 100644
--- a/basicsuite/mediaplayer/mediaplayer.pro
+++ b/basicsuite/mediaplayer/mediaplayer.pro
@@ -15,8 +15,8 @@ content.files = \
MetadataView.qml \
ParameterPanel.qml \
PlaybackControl.qml \
+ PlayerSlider.qml \
SeekControl.qml \
- Slider.qml \
UrlBar.qml \
VolumeControl.qml \
Effects \
diff --git a/basicsuite/mediaplayer/preview_l.jpg b/basicsuite/mediaplayer/preview_l.jpg
index f6cc262..98f528e 100644
--- a/basicsuite/mediaplayer/preview_l.jpg
+++ b/basicsuite/mediaplayer/preview_l.jpg
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/gallery.cpp b/basicsuite/qtquickcontrols2/gallery.cpp
new file mode 100644
index 0000000..04b988a
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/gallery.cpp
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+#include <QSettings>
+#include <QQuickStyle>
+#include <QIcon>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication::setApplicationName("Gallery");
+ QGuiApplication::setOrganizationName("QtProject");
+ QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+
+ QGuiApplication app(argc, argv);
+
+ QIcon::setThemeName("gallery");
+
+ QSettings settings;
+ QString style = QQuickStyle::name();
+ if (!style.isEmpty())
+ settings.setValue("style", style);
+ else
+ QQuickStyle::setStyle(settings.value("style").toString());
+
+ QQmlApplicationEngine engine;
+ engine.rootContext()->setContextProperty("availableStyles", QQuickStyle::availableStyles());
+ engine.load(QUrl("qrc:/main.qml"));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ return app.exec();
+}
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20/back.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20/back.png
new file mode 100644
index 0000000..db43e27
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20/back.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20/drawer.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20/drawer.png
new file mode 100644
index 0000000..1e974ef
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20/drawer.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20/menu.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20/menu.png
new file mode 100644
index 0000000..a10473d
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20/menu.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/back.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/back.png
new file mode 100644
index 0000000..c55ab31
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/back.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/drawer.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/drawer.png
new file mode 100644
index 0000000..eba3b6c
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/drawer.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/menu.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/menu.png
new file mode 100644
index 0000000..649c2a0
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@2/menu.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/back.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/back.png
new file mode 100644
index 0000000..b228eb8
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/back.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/drawer.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/drawer.png
new file mode 100644
index 0000000..3584ed6
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/drawer.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/menu.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/menu.png
new file mode 100644
index 0000000..9554b69
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@3/menu.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/back.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/back.png
new file mode 100644
index 0000000..dd157e7
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/back.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/drawer.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/drawer.png
new file mode 100644
index 0000000..60d93af
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/drawer.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/menu.png b/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/menu.png
new file mode 100644
index 0000000..187c171
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/20x20@4/menu.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/icons/gallery/index.theme b/basicsuite/qtquickcontrols2/icons/gallery/index.theme
new file mode 100644
index 0000000..a1bcd5e
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/icons/gallery/index.theme
@@ -0,0 +1,24 @@
+[Icon Theme]
+Name=Gallery
+Comment=Qt Quick Controls 2 Gallery Example Icon Theme
+
+Directories=20x20,20x20@2,20x20@3,20x20@4
+
+[20x20]
+Size=20
+Type=Fixed
+
+[20x20@2]
+Size=20
+Scale=2
+Type=Fixed
+
+[20x20@3]
+Size=20
+Scale=3
+Type=Fixed
+
+[20x20@4]
+Size=20
+Scale=4
+Type=Fixed
diff --git a/basicsuite/qtquickcontrols2/images/arrow.png b/basicsuite/qtquickcontrols2/images/arrow.png
new file mode 100644
index 0000000..d81accb
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/arrow.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/arrow@2x.png b/basicsuite/qtquickcontrols2/images/arrow@2x.png
new file mode 100644
index 0000000..309c018
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/arrow@2x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/arrow@3x.png b/basicsuite/qtquickcontrols2/images/arrow@3x.png
new file mode 100644
index 0000000..e146ecc
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/arrow@3x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/arrow@4x.png b/basicsuite/qtquickcontrols2/images/arrow@4x.png
new file mode 100644
index 0000000..b2ad27f
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/arrow@4x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/arrows.png b/basicsuite/qtquickcontrols2/images/arrows.png
new file mode 100644
index 0000000..d38b94a
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/arrows.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/arrows@2x.png b/basicsuite/qtquickcontrols2/images/arrows@2x.png
new file mode 100644
index 0000000..69b9342
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/arrows@2x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/arrows@3x.png b/basicsuite/qtquickcontrols2/images/arrows@3x.png
new file mode 100644
index 0000000..7703252
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/arrows@3x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/arrows@4x.png b/basicsuite/qtquickcontrols2/images/arrows@4x.png
new file mode 100644
index 0000000..66c1fac
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/arrows@4x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/qt-logo.png b/basicsuite/qtquickcontrols2/images/qt-logo.png
new file mode 100644
index 0000000..90e6f90
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/qt-logo.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/qt-logo@2x.png b/basicsuite/qtquickcontrols2/images/qt-logo@2x.png
new file mode 100644
index 0000000..22d111a
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/qt-logo@2x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/qt-logo@3x.png b/basicsuite/qtquickcontrols2/images/qt-logo@3x.png
new file mode 100644
index 0000000..627746c
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/qt-logo@3x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/images/qt-logo@4x.png b/basicsuite/qtquickcontrols2/images/qt-logo@4x.png
new file mode 100644
index 0000000..dc62286
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/images/qt-logo@4x.png
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/main.qml b/basicsuite/qtquickcontrols2/main.qml
new file mode 100644
index 0000000..473cdea
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/main.qml
@@ -0,0 +1,315 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.9
+import QtQuick.Layouts 1.3
+import QtQuick.Controls 2.3
+import QtQuick.Controls.Material 2.1
+import QtQuick.Controls.Universal 2.1
+import Qt.labs.settings 1.0
+
+Page {
+ id: window
+ width: 360
+ height: 520
+ visible: true
+
+ Settings {
+ id: settings
+ property string style: "Default"
+ }
+
+ Shortcut {
+ sequences: ["Esc", "Back"]
+ enabled: stackView.depth > 1
+ onActivated: {
+ stackView.pop()
+ listView.currentIndex = -1
+ }
+ }
+
+ Shortcut {
+ sequence: "Menu"
+ onActivated: optionsMenu.open()
+ }
+
+ header: ToolBar {
+ Material.foreground: "white"
+
+ RowLayout {
+ spacing: 20
+ anchors.fill: parent
+
+ ToolButton {
+ icon.name: stackView.depth > 1 ? "back" : "drawer"
+ onClicked: {
+ if (stackView.depth > 1) {
+ stackView.pop()
+ listView.currentIndex = -1
+ } else {
+ drawer.open()
+ }
+ }
+ }
+
+ Label {
+ id: titleLabel
+ text: listView.currentItem ? listView.currentItem.text : "Gallery"
+ font.pixelSize: 20
+ elide: Label.ElideRight
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+ Layout.fillWidth: true
+ }
+
+ ToolButton {
+ icon.name: "menu"
+ onClicked: optionsMenu.open()
+
+ Menu {
+ id: optionsMenu
+ x: parent.width - width
+ transformOrigin: Menu.TopRight
+
+ MenuItem {
+ text: "Settings"
+ onTriggered: settingsDialog.open()
+ }
+ MenuItem {
+ text: "About"
+ onTriggered: aboutDialog.open()
+ }
+ }
+ }
+ }
+ }
+
+ Drawer {
+ id: drawer
+ width: Math.min(window.width, window.height) / 3 * 2
+ height: window.height
+ interactive: stackView.depth === 1
+
+ ListView {
+ id: listView
+
+ focus: true
+ currentIndex: -1
+ anchors.fill: parent
+
+ delegate: ItemDelegate {
+ width: parent.width
+ text: model.title
+ highlighted: ListView.isCurrentItem
+ onClicked: {
+ listView.currentIndex = index
+ stackView.push("pages/" + model.source)
+ drawer.close()
+ }
+ }
+
+ model: ListModel {
+ ListElement { title: "BusyIndicator"; source: "BusyIndicatorPage.qml" }
+ ListElement { title: "Button"; source: "ButtonPage.qml" }
+ ListElement { title: "CheckBox"; source: "CheckBoxPage.qml" }
+ ListElement { title: "ComboBox"; source: "ComboBoxPage.qml" }
+ ListElement { title: "DelayButton"; source: "DelayButtonPage.qml" }
+ ListElement { title: "Dial"; source: "DialPage.qml" }
+ ListElement { title: "Dialog"; source: "DialogPage.qml" }
+ ListElement { title: "Delegates"; source: "DelegatePage.qml" }
+ ListElement { title: "Frame"; source: "FramePage.qml" }
+ ListElement { title: "GroupBox"; source: "GroupBoxPage.qml" }
+ ListElement { title: "PageIndicator"; source: "PageIndicatorPage.qml" }
+ ListElement { title: "ProgressBar"; source: "ProgressBarPage.qml" }
+ ListElement { title: "RadioButton"; source: "RadioButtonPage.qml" }
+ ListElement { title: "RangeSlider"; source: "RangeSliderPage.qml" }
+ ListElement { title: "ScrollBar"; source: "ScrollBarPage.qml" }
+ ListElement { title: "ScrollIndicator"; source: "ScrollIndicatorPage.qml" }
+ ListElement { title: "Slider"; source: "SliderPage.qml" }
+ ListElement { title: "SpinBox"; source: "SpinBoxPage.qml" }
+ ListElement { title: "StackView"; source: "StackViewPage.qml" }
+ ListElement { title: "SwipeView"; source: "SwipeViewPage.qml" }
+ ListElement { title: "Switch"; source: "SwitchPage.qml" }
+ ListElement { title: "TabBar"; source: "TabBarPage.qml" }
+ ListElement { title: "TextArea"; source: "TextAreaPage.qml" }
+ ListElement { title: "TextField"; source: "TextFieldPage.qml" }
+ ListElement { title: "ToolTip"; source: "ToolTipPage.qml" }
+ ListElement { title: "Tumbler"; source: "TumblerPage.qml" }
+ }
+
+ ScrollIndicator.vertical: ScrollIndicator { }
+ }
+ }
+
+ StackView {
+ id: stackView
+ anchors.fill: parent
+
+ initialItem: Pane {
+ id: pane
+
+ Image {
+ id: logo
+ width: pane.availableWidth / 2
+ height: pane.availableHeight / 2
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: -50
+ fillMode: Image.PreserveAspectFit
+ source: "images/qt-logo.png"
+ }
+
+ Label {
+ text: "Qt Quick Controls 2 provides a set of controls that can be used to build complete interfaces in Qt Quick."
+ anchors.margins: 20
+ anchors.top: logo.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: arrow.top
+ horizontalAlignment: Label.AlignHCenter
+ verticalAlignment: Label.AlignVCenter
+ wrapMode: Label.Wrap
+ }
+
+ Image {
+ id: arrow
+ source: "images/arrow.png"
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ }
+ }
+ }
+
+ Dialog {
+ id: settingsDialog
+ x: Math.round((window.width - width) / 2)
+ y: Math.round(window.height / 6)
+ width: Math.round(Math.min(window.width, window.height) / 3 * 2)
+ modal: true
+ focus: true
+ title: "Settings"
+
+ standardButtons: Dialog.Ok | Dialog.Cancel
+ onAccepted: {
+ settings.style = styleBox.displayText
+ settingsDialog.close()
+ }
+ onRejected: {
+ styleBox.currentIndex = styleBox.styleIndex
+ settingsDialog.close()
+ }
+
+ contentItem: ColumnLayout {
+ id: settingsColumn
+ spacing: 20
+
+ RowLayout {
+ spacing: 10
+
+ Label {
+ text: "Style:"
+ }
+
+ ComboBox {
+ id: styleBox
+ property int styleIndex: -1
+ model: availableStyles
+ Component.onCompleted: {
+ styleIndex = find(settings.style, Qt.MatchFixedString)
+ if (styleIndex !== -1)
+ currentIndex = styleIndex
+ }
+ Layout.fillWidth: true
+ }
+ }
+
+ Label {
+ text: "Restart required"
+ color: "#e41e25"
+ opacity: styleBox.currentIndex !== styleBox.styleIndex ? 1.0 : 0.0
+ horizontalAlignment: Label.AlignHCenter
+ verticalAlignment: Label.AlignVCenter
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+ }
+
+ Dialog {
+ id: aboutDialog
+ modal: true
+ focus: true
+ title: "About"
+ x: (window.width - width) / 2
+ y: window.height / 6
+ width: Math.min(window.width, window.height) / 3 * 2
+ contentHeight: aboutColumn.height
+
+ Column {
+ id: aboutColumn
+ spacing: 20
+
+ Label {
+ width: aboutDialog.availableWidth
+ text: "The Qt Quick Controls 2 module delivers the next generation user interface controls based on Qt Quick."
+ wrapMode: Label.Wrap
+ font.pixelSize: 12
+ }
+
+ Label {
+ width: aboutDialog.availableWidth
+ text: "In comparison to the desktop-oriented Qt Quick Controls 1, Qt Quick Controls 2 "
+ + "are an order of magnitude simpler, lighter and faster, and are primarily targeted "
+ + "towards embedded and mobile platforms."
+ wrapMode: Label.Wrap
+ font.pixelSize: 12
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/BusyIndicatorPage.qml b/basicsuite/qtquickcontrols2/pages/BusyIndicatorPage.qml
new file mode 100644
index 0000000..3fbae0c
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/BusyIndicatorPage.qml
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "BusyIndicator is used to indicate activity while content is being loaded,"
+ + " or when the UI is blocked waiting for a resource to become available."
+ }
+
+ BusyIndicator {
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/ButtonPage.qml b/basicsuite/qtquickcontrols2/pages/ButtonPage.qml
new file mode 100644
index 0000000..7ebceb4
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/ButtonPage.qml
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Layouts 1.0
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "Button presents a push-button that can be pushed or clicked by the user. "
+ + "Buttons are normally used to perform an action, or to answer a question."
+ }
+
+ ColumnLayout {
+ spacing: 20
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Button {
+ text: "First"
+ Layout.fillWidth: true
+ }
+ Button {
+ id: button
+ text: "Second"
+ highlighted: true
+ Layout.fillWidth: true
+ }
+ Button {
+ text: "Third"
+ enabled: false
+ Layout.fillWidth: true
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/CheckBoxPage.qml b/basicsuite/qtquickcontrols2/pages/CheckBoxPage.qml
new file mode 100644
index 0000000..a8a1692
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/CheckBoxPage.qml
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "CheckBox presents an option button that can be toggled on or off. "
+ + "Check boxes are typically used to select one or more options from a set of options."
+ }
+
+ Column {
+ spacing: 20
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ CheckBox {
+ text: "First"
+ checked: true
+ }
+ CheckBox {
+ text: "Second"
+ }
+ CheckBox {
+ text: "Third"
+ checked: true
+ enabled: false
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/ComboBoxPage.qml b/basicsuite/qtquickcontrols2/pages/ComboBoxPage.qml
new file mode 100644
index 0000000..bc89e99
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/ComboBoxPage.qml
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.2
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "ComboBox is a combined button and popup list. It presents "
+ + "a list of options to the user that occupies minimal screen space."
+ }
+
+ ComboBox {
+ model: ["First", "Second", "Third"]
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "ComboBox can be made \l editable. An editable combo box auto-"
+ + "completes its text based on what is available in the model."
+ }
+
+ ComboBox {
+ editable: true
+ model: ListModel {
+ id: model
+ ListElement { text: "Banana" }
+ ListElement { text: "Apple" }
+ ListElement { text: "Coconut" }
+ }
+ onAccepted: {
+ if (find(editText) === -1)
+ model.append({text: editText})
+ }
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/DelayButtonPage.qml b/basicsuite/qtquickcontrols2/pages/DelayButtonPage.qml
new file mode 100644
index 0000000..974dcab
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/DelayButtonPage.qml
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.2
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "DelayButton is a checkable button that incorporates a delay before the "
+ + "button is activated. This delay prevents accidental presses."
+ }
+
+ DelayButton {
+ text: "DelayButton"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/DelegatePage.qml b/basicsuite/qtquickcontrols2/pages/DelegatePage.qml
new file mode 100644
index 0000000..bf07e1b
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/DelegatePage.qml
@@ -0,0 +1,218 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.1
+
+Pane {
+ padding: 0
+
+ property var delegateComponentMap: {
+ "ItemDelegate": itemDelegateComponent,
+ "SwipeDelegate": swipeDelegateComponent,
+ "CheckDelegate": checkDelegateComponent,
+ "RadioDelegate": radioDelegateComponent,
+ "SwitchDelegate": switchDelegateComponent
+ }
+
+ Component {
+ id: itemDelegateComponent
+
+ ItemDelegate {
+ text: labelText
+ width: parent.width
+ }
+ }
+
+ Component {
+ id: swipeDelegateComponent
+
+ SwipeDelegate {
+ id: swipeDelegate
+ text: labelText
+ width: parent.width
+
+ Component {
+ id: removeComponent
+
+ Rectangle {
+ color: SwipeDelegate.pressed ? "#333" : "#444"
+ width: parent.width
+ height: parent.height
+ clip: true
+
+ SwipeDelegate.onClicked: view.model.remove(ourIndex)
+
+ Label {
+ font.pixelSize: swipeDelegate.font.pixelSize
+ text: "Remove"
+ color: "white"
+ anchors.centerIn: parent
+ }
+ }
+ }
+
+ swipe.left: removeComponent
+ swipe.right: removeComponent
+ }
+ }
+
+ Component {
+ id: checkDelegateComponent
+
+ CheckDelegate {
+ text: labelText
+ width: parent.width
+ }
+ }
+
+ ButtonGroup {
+ id: radioButtonGroup
+ }
+
+ Component {
+ id: radioDelegateComponent
+
+ RadioDelegate {
+ text: labelText
+ ButtonGroup.group: radioButtonGroup
+ }
+ }
+
+ Component {
+ id: switchDelegateComponent
+
+ SwitchDelegate {
+ text: labelText
+ }
+ }
+
+ ColumnLayout {
+ id: column
+ spacing: 40
+ anchors.fill: parent
+ anchors.topMargin: 20
+
+ Label {
+ Layout.fillWidth: true
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "Delegate controls are used as delegates in views such as ListView."
+ }
+
+ ListView {
+ id: listView
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ clip: true
+ model: ListModel {
+ ListElement { type: "ItemDelegate"; text: "ItemDelegate" }
+ ListElement { type: "ItemDelegate"; text: "ItemDelegate" }
+ ListElement { type: "ItemDelegate"; text: "ItemDelegate" }
+ ListElement { type: "SwipeDelegate"; text: "SwipeDelegate" }
+ ListElement { type: "SwipeDelegate"; text: "SwipeDelegate" }
+ ListElement { type: "SwipeDelegate"; text: "SwipeDelegate" }
+ ListElement { type: "CheckDelegate"; text: "CheckDelegate" }
+ ListElement { type: "CheckDelegate"; text: "CheckDelegate" }
+ ListElement { type: "CheckDelegate"; text: "CheckDelegate" }
+ ListElement { type: "RadioDelegate"; text: "RadioDelegate" }
+ ListElement { type: "RadioDelegate"; text: "RadioDelegate" }
+ ListElement { type: "RadioDelegate"; text: "RadioDelegate" }
+ ListElement { type: "SwitchDelegate"; text: "SwitchDelegate" }
+ ListElement { type: "SwitchDelegate"; text: "SwitchDelegate" }
+ ListElement { type: "SwitchDelegate"; text: "SwitchDelegate" }
+ }
+
+ section.property: "type"
+ section.delegate: Pane {
+ width: listView.width
+ height: sectionLabel.implicitHeight + 20
+
+ Label {
+ id: sectionLabel
+ text: section
+ anchors.centerIn: parent
+ }
+ }
+
+ delegate: Loader {
+ id: delegateLoader
+ width: listView.width
+ sourceComponent: delegateComponentMap[text]
+
+ property string labelText: text
+ property ListView view: listView
+ property int ourIndex: index
+
+ // Can't find a way to do this in the SwipeDelegate component itself,
+ // so do it here instead.
+ ListView.onRemove: SequentialAnimation {
+ PropertyAction {
+ target: delegateLoader
+ property: "ListView.delayRemove"
+ value: true
+ }
+ NumberAnimation {
+ target: item
+ property: "height"
+ to: 0
+ easing.type: Easing.InOutQuad
+ }
+ PropertyAction {
+ target: delegateLoader
+ property: "ListView.delayRemove"
+ value: false
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/DialPage.qml b/basicsuite/qtquickcontrols2/pages/DialPage.qml
new file mode 100644
index 0000000..a8fa976
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/DialPage.qml
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "The Dial is similar to a traditional dial knob that is found on devices such as "
+ + "stereos or industrial equipment. It allows the user to specify a value within a range."
+ }
+
+ Dial {
+ value: 0.5
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/DialogPage.qml b/basicsuite/qtquickcontrols2/pages/DialogPage.qml
new file mode 100644
index 0000000..8bade21
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/DialogPage.qml
@@ -0,0 +1,226 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Layouts 1.0
+import QtQuick.Controls 2.3
+
+ScrollablePage {
+ id: page
+
+ readonly property int buttonWidth: Math.max(button.implicitWidth, Math.min(button.implicitWidth * 2, page.availableWidth / 3))
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "Dialog is a popup that is mostly used for short-term tasks "
+ + "and brief communications with the user."
+ }
+
+ Button {
+ text: "Message"
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: buttonWidth
+ onClicked: messageDialog.open()
+
+ Dialog {
+ id: messageDialog
+
+ x: (parent.width - width) / 2
+ y: (parent.height - height) / 2
+
+ title: "Message"
+
+ Label {
+ text: "Lorem ipsum dolor sit amet..."
+ }
+ }
+ }
+
+ Button {
+ id: button
+ text: "Confirmation"
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: buttonWidth
+ onClicked: confirmationDialog.open()
+
+ Dialog {
+ id: confirmationDialog
+
+ x: (parent.width - width) / 2
+ y: (parent.height - height) / 2
+ parent: Overlay.overlay
+
+ modal: true
+ title: "Confirmation"
+ standardButtons: Dialog.Yes | Dialog.No
+
+ Column {
+ spacing: 20
+ anchors.fill: parent
+ Label {
+ text: "The document has been modified.\nDo you want to save your changes?"
+ }
+ CheckBox {
+ text: "Do not ask again"
+ anchors.right: parent.right
+ }
+ }
+ }
+ }
+
+ Button {
+ text: "Content"
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: buttonWidth
+ onClicked: contentDialog.open()
+
+ Dialog {
+ id: contentDialog
+
+ x: (parent.width - width) / 2
+ y: (parent.height - height) / 2
+ width: Math.min(page.width, page.height) / 3 * 2
+ contentHeight: logo.height * 2
+ parent: Overlay.overlay
+
+ modal: true
+ title: "Content"
+ standardButtons: Dialog.Close
+
+ Flickable {
+ id: flickable
+ clip: true
+ anchors.fill: parent
+ contentHeight: column.height
+
+ Column {
+ id: column
+ spacing: 20
+ width: parent.width
+
+ Image {
+ id: logo
+ width: parent.width / 2
+ anchors.horizontalCenter: parent.horizontalCenter
+ fillMode: Image.PreserveAspectFit
+ source: "../images/qt-logo.png"
+ }
+
+ Label {
+ width: parent.width
+ text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc finibus "
+ + "in est quis laoreet. Interdum et malesuada fames ac ante ipsum primis "
+ + "in faucibus. Curabitur eget justo sollicitudin enim faucibus bibendum. "
+ + "Suspendisse potenti. Vestibulum cursus consequat mauris id sollicitudin. "
+ + "Duis facilisis hendrerit consectetur. Curabitur sapien tortor, efficitur "
+ + "id auctor nec, efficitur et nisl. Ut venenatis eros in nunc placerat, "
+ + "eu aliquam enim suscipit."
+ wrapMode: Label.Wrap
+ }
+ }
+
+ ScrollIndicator.vertical: ScrollIndicator {
+ parent: contentDialog.contentItem
+ anchors.top: flickable.top
+ anchors.bottom: flickable.bottom
+ anchors.right: parent.right
+ anchors.rightMargin: -contentDialog.rightPadding + 1
+ }
+ }
+ }
+ }
+
+ Button {
+ text: "Input"
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: buttonWidth
+ onClicked: inputDialog.open()
+
+ Dialog {
+ id: inputDialog
+
+ x: (parent.width - width) / 2
+ y: (parent.height - height) / 2
+ parent: Overlay.overlay
+
+ focus: true
+ modal: true
+ title: "Input"
+ standardButtons: Dialog.Ok | Dialog.Cancel
+
+ ColumnLayout {
+ spacing: 20
+ anchors.fill: parent
+ Label {
+ elide: Label.ElideRight
+ text: "Please enter the credentials:"
+ Layout.fillWidth: true
+ }
+ TextField {
+ focus: true
+ placeholderText: "Username"
+ Layout.fillWidth: true
+ }
+ TextField {
+ placeholderText: "Password"
+ echoMode: TextField.PasswordEchoOnEdit
+ Layout.fillWidth: true
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/FramePage.qml b/basicsuite/qtquickcontrols2/pages/FramePage.qml
new file mode 100644
index 0000000..a1d7d7f
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/FramePage.qml
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ readonly property int itemWidth: Math.max(button.implicitWidth, Math.min(button.implicitWidth * 3, page.availableWidth / 3 * 2))
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "Frame is used to layout a logical group of controls together, within a visual frame."
+ }
+
+ Frame {
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Column {
+ spacing: 20
+ width: page.itemWidth
+
+ RadioButton {
+ text: "First"
+ checked: true
+ width: parent.width
+ }
+ RadioButton {
+ id: button
+ text: "Second"
+ width: parent.width
+ }
+ RadioButton {
+ text: "Third"
+ width: parent.width
+ }
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/GroupBoxPage.qml b/basicsuite/qtquickcontrols2/pages/GroupBoxPage.qml
new file mode 100644
index 0000000..bb332a8
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/GroupBoxPage.qml
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ readonly property int itemWidth: Math.max(button.implicitWidth, Math.min(button.implicitWidth * 3, page.availableWidth / 3 * 2))
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "A GroupBox provides a frame, a title on top of it, and a logical group of controls within that frame."
+ }
+
+ GroupBox {
+ title: "Title"
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Column {
+ spacing: 20
+ width: page.itemWidth
+
+ RadioButton {
+ text: "First"
+ checked: true
+ width: parent.width
+ }
+ RadioButton {
+ id: button
+ text: "Second"
+ width: parent.width
+ }
+ RadioButton {
+ text: "Third"
+ width: parent.width
+ }
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/PageIndicatorPage.qml b/basicsuite/qtquickcontrols2/pages/PageIndicatorPage.qml
new file mode 100644
index 0000000..ed35c07
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/PageIndicatorPage.qml
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "PageIndicator is used to indicate the currently active page in a container of pages."
+ }
+
+ PageIndicator {
+ count: 5
+ currentIndex: 2
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/ProgressBarPage.qml b/basicsuite/qtquickcontrols2/pages/ProgressBarPage.qml
new file mode 100644
index 0000000..36bba90
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/ProgressBarPage.qml
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "ProgressBar indicates the progress of an operation. It can be set in an "
+ + "indeterminate mode to indicate that the length of the operation is unknown."
+ }
+
+ ProgressBar {
+ id: bar
+ value: 0.5
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ ProgressBar {
+ indeterminate: true
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/RadioButtonPage.qml b/basicsuite/qtquickcontrols2/pages/RadioButtonPage.qml
new file mode 100644
index 0000000..451caf5
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/RadioButtonPage.qml
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "RadioButton presents an option button that can be toggled on or off. "
+ + "Radio buttons are typically used to select one option from a set of options."
+ }
+
+ Column {
+ spacing: 20
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ RadioButton {
+ text: "First"
+ }
+ RadioButton {
+ text: "Second"
+ checked: true
+ }
+ RadioButton {
+ text: "Third"
+ enabled: false
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/RangeSliderPage.qml b/basicsuite/qtquickcontrols2/pages/RangeSliderPage.qml
new file mode 100644
index 0000000..9e8d9a9
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/RangeSliderPage.qml
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "RangeSlider is used to select a range specified by two values, by sliding each handle along a track."
+ }
+
+ RangeSlider {
+ id: slider
+ first.value: 0.25
+ second.value: 0.75
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ RangeSlider {
+ orientation: Qt.Vertical
+ first.value: 0.25
+ second.value: 0.75
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/ScrollBarPage.qml b/basicsuite/qtquickcontrols2/pages/ScrollBarPage.qml
new file mode 100644
index 0000000..aaa1d8e
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/ScrollBarPage.qml
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+Flickable {
+ id: flickable
+
+ contentHeight: pane.height
+
+ Pane {
+ id: pane
+ width: flickable.width
+ height: flickable.height * 1.25
+
+ Column {
+ id: column
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "ScrollBar is an interactive bar that can be used to scroll to a specific position. "
+ + "A scroll bar can be either vertical or horizontal, and can be attached to any Flickable, "
+ + "such as ListView and GridView."
+ }
+
+ Image {
+ rotation: 90
+ source: "../images/arrows.png"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+
+ ScrollBar.vertical: ScrollBar { }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/ScrollIndicatorPage.qml b/basicsuite/qtquickcontrols2/pages/ScrollIndicatorPage.qml
new file mode 100644
index 0000000..89b8bc1
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/ScrollIndicatorPage.qml
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+Flickable {
+ id: flickable
+
+ contentHeight: pane.height
+
+ Pane {
+ id: pane
+ width: flickable.width
+ height: flickable.height * 1.25
+
+ Column {
+ id: column
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "ScrollIndicator is a non-interactive indicator that indicates the current scroll position. "
+ + "A scroll indicator can be either vertical or horizontal, and can be attached to any Flickable, "
+ + "such as ListView and GridView."
+ }
+
+ Image {
+ rotation: 90
+ source: "../images/arrows.png"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+
+ ScrollIndicator.vertical: ScrollIndicator { }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/ScrollablePage.qml b/basicsuite/qtquickcontrols2/pages/ScrollablePage.qml
new file mode 100644
index 0000000..90bd014
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/ScrollablePage.qml
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.0
+
+Page {
+ id: page
+
+ default property alias content: pane.contentItem
+
+ Flickable {
+ anchors.fill: parent
+ contentHeight: pane.implicitHeight
+ flickableDirection: Flickable.AutoFlickIfNeeded
+
+ Pane {
+ id: pane
+ width: parent.width
+ }
+
+ ScrollIndicator.vertical: ScrollIndicator { }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/SliderPage.qml b/basicsuite/qtquickcontrols2/pages/SliderPage.qml
new file mode 100644
index 0000000..9d941d0
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/SliderPage.qml
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "Slider is used to select a value by sliding a handle along a track."
+ }
+
+ Slider {
+ id: slider
+ value: 0.5
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ Slider {
+ orientation: Qt.Vertical
+ value: 0.5
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/SpinBoxPage.qml b/basicsuite/qtquickcontrols2/pages/SpinBoxPage.qml
new file mode 100644
index 0000000..b063a37
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/SpinBoxPage.qml
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "SpinBox allows the user to choose an integer value by clicking the up or down indicator buttons, "
+ + "by pressing up or down on the keyboard, or by entering a text value in the input field."
+ }
+
+ SpinBox {
+ id: box
+ value: 50
+ anchors.horizontalCenter: parent.horizontalCenter
+ editable: true
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/StackViewPage.qml b/basicsuite/qtquickcontrols2/pages/StackViewPage.qml
new file mode 100644
index 0000000..45884a7
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/StackViewPage.qml
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+StackView {
+ id: stackView
+ initialItem: page
+
+ Component {
+ id: page
+
+ Pane {
+ id: pane
+ width: parent ? parent.width : 0 // TODO: fix null parent on destruction
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "StackView provides a stack-based navigation model which can be used with a set of interlinked pages. "
+ + "Items are pushed onto the stack as the user navigates deeper into the material, and popped off again "
+ + "when he chooses to go back."
+ }
+
+ Button {
+ id: button
+ text: "Push"
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.max(button.implicitWidth, Math.min(button.implicitWidth * 2, pane.availableWidth / 3))
+ onClicked: stackView.push(page)
+ }
+
+ Button {
+ text: "Pop"
+ enabled: stackView.depth > 1
+ width: Math.max(button.implicitWidth, Math.min(button.implicitWidth * 2, pane.availableWidth / 3))
+ anchors.horizontalCenter: parent.horizontalCenter
+ onClicked: stackView.pop()
+ }
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/SwipeViewPage.qml b/basicsuite/qtquickcontrols2/pages/SwipeViewPage.qml
new file mode 100644
index 0000000..d09ac50
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/SwipeViewPage.qml
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+Pane {
+ id: pane
+
+ SwipeView {
+ id: view
+ currentIndex: 1
+ anchors.fill: parent
+
+ Repeater {
+ model: 3
+
+ Pane {
+ width: view.width
+ height: view.height
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "SwipeView provides a navigation model that simplifies horizontal paged scrolling. "
+ + "The page indicator on the bottom shows which is the presently active page."
+ }
+
+ Image {
+ source: "../images/arrows.png"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+ }
+ }
+
+ PageIndicator {
+ count: view.count
+ currentIndex: view.currentIndex
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/SwitchPage.qml b/basicsuite/qtquickcontrols2/pages/SwitchPage.qml
new file mode 100644
index 0000000..d894ed5
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/SwitchPage.qml
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "Switch is an option button that can be dragged or toggled on or off. "
+ + "Switches are typically used to select between two states."
+ }
+
+ Column {
+ spacing: 20
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Switch {
+ text: "First"
+ }
+ Switch {
+ text: "Second"
+ checked: true
+ }
+ Switch {
+ text: "Third"
+ enabled: false
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/TabBarPage.qml b/basicsuite/qtquickcontrols2/pages/TabBarPage.qml
new file mode 100644
index 0000000..16200fc
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/TabBarPage.qml
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+Page {
+ id: page
+
+ SwipeView {
+ id: swipeView
+ anchors.fill: parent
+ currentIndex: tabBar.currentIndex
+
+ Repeater {
+ model: 3
+
+ Pane {
+ width: swipeView.width
+ height: swipeView.height
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "TabBar is a bar with icons or text which allows the user"
+ + "to switch between different subtasks, views, or modes."
+ }
+
+ Image {
+ source: "../images/arrows.png"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+ }
+ }
+
+ footer: TabBar {
+ id: tabBar
+ currentIndex: swipeView.currentIndex
+
+ TabButton {
+ text: "First"
+ }
+ TabButton {
+ text: "Second"
+ }
+ TabButton {
+ text: "Third"
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/TextAreaPage.qml b/basicsuite/qtquickcontrols2/pages/TextAreaPage.qml
new file mode 100644
index 0000000..d83096d
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/TextAreaPage.qml
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "TextArea is a multi-line text editor."
+ }
+
+ TextArea {
+ width: Math.max(implicitWidth, Math.min(implicitWidth * 3, pane.availableWidth / 3))
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ wrapMode: TextArea.Wrap
+ text: "TextArea\n...\n...\n..."
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/TextFieldPage.qml b/basicsuite/qtquickcontrols2/pages/TextFieldPage.qml
new file mode 100644
index 0000000..959d968
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/TextFieldPage.qml
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "TextField is a single-line text editor."
+ }
+
+ TextField {
+ id: field
+ placeholderText: "TextField"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/ToolTipPage.qml b/basicsuite/qtquickcontrols2/pages/ToolTipPage.qml
new file mode 100644
index 0000000..bc2aa81
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/ToolTipPage.qml
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "A tool tip is a short piece of text that informs the user of a control's function."
+ }
+
+ Button {
+ text: "Tip"
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ ToolTip.timeout: 5000
+ ToolTip.visible: pressed
+ ToolTip.text: "This is a tool tip."
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/pages/TumblerPage.qml b/basicsuite/qtquickcontrols2/pages/TumblerPage.qml
new file mode 100644
index 0000000..bc0aa24
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/pages/TumblerPage.qml
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.6
+import QtQuick.Controls 2.1
+
+ScrollablePage {
+ id: page
+
+ Column {
+ spacing: 40
+ width: parent.width
+
+ Label {
+ width: parent.width
+ wrapMode: Label.Wrap
+ horizontalAlignment: Qt.AlignHCenter
+ text: "Tumbler is used to select a value by spinning a wheel."
+ }
+
+ Tumbler {
+ model: 10
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/basicsuite/qtquickcontrols2/preview_l.jpg b/basicsuite/qtquickcontrols2/preview_l.jpg
new file mode 100644
index 0000000..c840e8e
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/preview_l.jpg
Binary files differ
diff --git a/basicsuite/qtquickcontrols2/qtquickcontrols2.conf b/basicsuite/qtquickcontrols2/qtquickcontrols2.conf
new file mode 100644
index 0000000..da1a8f4
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/qtquickcontrols2.conf
@@ -0,0 +1,8 @@
+[Material]
+Primary=#41cd52
+Accent=#41cd52
+Theme=System
+
+[Universal]
+Accent=#41cd52
+Theme=System
diff --git a/basicsuite/qtquickcontrols2/qtquickcontrols2.pro b/basicsuite/qtquickcontrols2/qtquickcontrols2.pro
new file mode 100644
index 0000000..1d3eac1
--- /dev/null
+++ b/basicsuite/qtquickcontrols2/qtquickcontrols2.pro
@@ -0,0 +1,18 @@
+TARGET = qtquickcontrols2
+
+include(../shared/shared.pri)
+b2qtdemo_deploy_defaults()
+
+content.files = \
+ $$PWD/preview_l.jpg \
+ $$PWD/main.qml \
+ $$PWD/qtquickcontrols2.conf \
+ $$PWD/images \
+ $$PWD/icons \
+ $$PWD/pages
+
+content.path = $$DESTPATH
+
+OTHER_FILES += $${content.files}
+
+INSTALLS += target content
diff --git a/basicsuite/qtwebbrowser/preview_l.jpg b/basicsuite/qtwebbrowser/preview_l.jpg
index e96d576..d4ddedc 100644
--- a/basicsuite/qtwebbrowser/preview_l.jpg
+++ b/basicsuite/qtwebbrowser/preview_l.jpg
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser
deleted file mode 160000
-Subproject c86bb8a400f79be205025598310926df5d01c7b
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/00_HomePage.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/00_HomePage.png
new file mode 100644
index 0000000..cf7c69c
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/00_HomePage.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/00_HomePage_DeleteBookmarks.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/00_HomePage_DeleteBookmarks.png
new file mode 100644
index 0000000..95441e4
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/00_HomePage_DeleteBookmarks.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing.png
new file mode 100644
index 0000000..d93a211
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Bookmark.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Bookmark.png
new file mode 100644
index 0000000..0b880b8
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Bookmark.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Loading.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Loading.png
new file mode 100644
index 0000000..016b7f7
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Loading.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_NoKb.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_NoKb.png
new file mode 100644
index 0000000..e330c6c
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_NoKb.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Private.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Private.png
new file mode 100644
index 0000000..50d1115
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_Browsing_Private.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_URLbox+Bookmark.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_URLbox+Bookmark.png
new file mode 100644
index 0000000..1d11050
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_URLbox+Bookmark.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_URLbox.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_URLbox.png
new file mode 100644
index 0000000..eec1ae1
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/01_URLbox.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_Link.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_Link.png
new file mode 100644
index 0000000..7edffa0
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_Link.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_Text.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_Text.png
new file mode 100644
index 0000000..476fcbc
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_Text.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_URL.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_URL.png
new file mode 100644
index 0000000..861e774
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/02_Selection_URL.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_1.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_1.png
new file mode 100644
index 0000000..80a9a77
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_1.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_10+.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_10+.png
new file mode 100644
index 0000000..58c833e
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_10+.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_2.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_2.png
new file mode 100644
index 0000000..f4da8ae
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_2.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_3.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_3.png
new file mode 100644
index 0000000..4d89730
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_3.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_4.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_4.png
new file mode 100644
index 0000000..c4c74a0
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_4.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_5+.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_5+.png
new file mode 100644
index 0000000..ccfc920
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_5+.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_5.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_5.png
new file mode 100644
index 0000000..451dec4
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_5.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_New.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_New.png
new file mode 100644
index 0000000..08ef407
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/03_Tabs_New.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/04_Settings.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/04_Settings.png
new file mode 100644
index 0000000..0de35da
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/04_Settings.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/Bookmarks_Button.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/Bookmarks_Button.png
new file mode 100644
index 0000000..83849d3
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/Bookmarks_Button.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/LightWebBrowserFunctions.xlsx b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/LightWebBrowserFunctions.xlsx
new file mode 100644
index 0000000..037813a
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/LightWebBrowserFunctions.xlsx
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/LightWebBrowser_Specs.pdf b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/LightWebBrowser_Specs.pdf
new file mode 100644
index 0000000..8f77fe5
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/mockups/LightWebBrowser_Specs.pdf
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/qtwebbrowser.pro b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/qtwebbrowser.pro
new file mode 100644
index 0000000..65d9262
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/qtwebbrowser.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS = src
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/appengine.cpp b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/appengine.cpp
new file mode 100644
index 0000000..7be0e5b
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/appengine.cpp
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "appengine.h"
+
+#include <QtCore/QDir>
+#include <QtCore/QStandardPaths>
+#include <QStringBuilder>
+#include <QCoreApplication>
+
+AppEngine::AppEngine(QObject *parent)
+ : QObject(parent)
+ , m_settings(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % QDir::separator() % "settings.ini", QSettings::IniFormat, this)
+{
+ foreach (const QString &arg, QCoreApplication::arguments().mid(1)) {
+ if (arg.startsWith('-'))
+ continue;
+ const QUrl url(arg);
+ if (url.isValid()) {
+ m_initialUrl = url.toString();
+ break;
+ }
+ }
+}
+
+QString AppEngine::settingsPath()
+{
+ return m_settings.fileName();
+}
+
+QString AppEngine::initialUrl() const
+{
+ return m_initialUrl;
+}
+
+QUrl AppEngine::fromUserInput(const QString& userInput)
+{
+ QFileInfo fileInfo(userInput);
+ if (fileInfo.exists())
+ return QUrl::fromLocalFile(fileInfo.absoluteFilePath());
+ return QUrl::fromUserInput(userInput);
+}
+
+bool AppEngine::isUrl(const QString& userInput)
+{
+ if (userInput.startsWith(QStringLiteral("www."))
+ || userInput.startsWith(QStringLiteral("http"))
+ || userInput.startsWith(QStringLiteral("ftp"))
+ || userInput.contains(QStringLiteral("://"))
+ || userInput.endsWith(QStringLiteral(".com")))
+ return true;
+ return false;
+}
+
+QString AppEngine::domainFromString(const QString& urlString)
+{
+ return QUrl::fromUserInput(urlString).host();
+}
+
+QString AppEngine::fallbackColor()
+{
+ static QList<QString> colors = QList<QString>() << QStringLiteral("#46a2da")
+ << QStringLiteral("#18394c")
+ << QStringLiteral("#ff8c0a")
+ << QStringLiteral("#5caa15");
+ static int index = -1;
+ if (++index == colors.count())
+ index = 0;
+ return colors[index];
+}
+
+QString AppEngine::restoreSetting(const QString &name, const QString &defaultValue)
+{
+ return m_settings.value(name, defaultValue).toString();
+}
+
+void AppEngine::saveSetting(const QString &name, const QString &value)
+{
+ m_settings.setValue(name, value);
+}
+
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/appengine.h b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/appengine.h
new file mode 100644
index 0000000..808f3b7
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/appengine.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef APPENGINE_H
+#define APPENGINE_H
+
+#include <QtCore/QEvent>
+#include <QtCore/QFileInfo>
+#include <QtCore/QSettings>
+#include <QtCore/QUrl>
+#include <QtGui/QColor>
+#include <QtQuick/QQuickItemGrabResult>
+
+namespace utils {
+inline bool isTouchEvent(const QEvent* event)
+{
+ switch (event->type()) {
+ case QEvent::TouchBegin:
+ case QEvent::TouchUpdate:
+ case QEvent::TouchEnd:
+ return true;
+ default:
+ return false;
+ }
+}
+
+inline bool isMouseEvent(const QEvent* event)
+{
+ switch (event->type()) {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseMove:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseButtonDblClick:
+ return true;
+ default:
+ return false;
+ }
+}
+
+}
+
+class AppEngine : public QObject {
+ Q_OBJECT
+
+ Q_PROPERTY(QString settingsPath READ settingsPath FINAL CONSTANT)
+ Q_PROPERTY(QString initialUrl READ initialUrl FINAL CONSTANT)
+
+public:
+ AppEngine(QObject *parent = 0);
+
+ QString settingsPath();
+ QString initialUrl() const;
+
+ Q_INVOKABLE bool isUrl(const QString& userInput);
+ Q_INVOKABLE QUrl fromUserInput(const QString& userInput);
+ Q_INVOKABLE QString domainFromString(const QString& urlString);
+ Q_INVOKABLE QString fallbackColor();
+ Q_INVOKABLE QString restoreSetting(const QString &name, const QString &defaultValue = QString());
+ Q_INVOKABLE void saveSetting(const QString &name, const QString &value);
+
+private:
+ QSettings m_settings;
+ QString m_initialUrl;
+};
+
+#endif // APPENGINE_H
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/main.cpp b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/main.cpp
new file mode 100644
index 0000000..919629c
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/main.cpp
@@ -0,0 +1,116 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "appengine.h"
+#include "navigationhistoryproxymodel.h"
+#include "touchtracker.h"
+
+#if defined(DESKTOP_BUILD)
+#include "touchmockingapplication.h"
+#endif
+
+#include <QGuiApplication>
+#include <QQmlContext>
+#include <QQmlEngine>
+#include <QQuickView>
+#include <QtWebEngine/qtwebengineglobal.h>
+
+static QObject *engine_factory(QQmlEngine *engine, QJSEngine *scriptEngine)
+{
+ Q_UNUSED(engine);
+ Q_UNUSED(scriptEngine);
+ AppEngine *eng = new AppEngine();
+ return eng;
+}
+
+int main(int argc, char **argv)
+{
+ qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
+
+ //do not use any plugins installed on the device
+ qputenv("QML2_IMPORT_PATH", QByteArray());
+
+ // We use touch mocking on desktop and apply all the mobile switches.
+ QByteArrayList args = QByteArrayList()
+ << QByteArrayLiteral("--enable-embedded-switches")
+ << QByteArrayLiteral("--log-level=0");
+ const int count = args.size() + argc;
+ QVector<char*> qargv(count);
+
+ qargv[0] = argv[0];
+ for (int i = 0; i < args.size(); ++i)
+ qargv[i + 1] = args[i].data();
+ for (int i = args.size() + 1; i < count; ++i)
+ qargv[i] = argv[i - args.size()];
+
+ int qAppArgCount = qargv.size();
+
+#if defined(DESKTOP_BUILD)
+ TouchMockingApplication app(qAppArgCount, qargv.data());
+#else
+ QGuiApplication app(qAppArgCount, qargv.data());
+#endif
+
+ qmlRegisterType<NavigationHistoryProxyModel>("WebBrowser", 1, 0, "SearchProxyModel");
+ qmlRegisterType<TouchTracker>("WebBrowser", 1, 0, "TouchTracker");
+ qmlRegisterSingletonType<AppEngine>("WebBrowser", 1, 0, "AppEngine", engine_factory);
+
+ QtWebEngine::initialize();
+
+ app.setOrganizationName("The Qt Company");
+ app.setOrganizationDomain("qt.io");
+ app.setApplicationName("qtwebbrowser");
+
+ QQuickView view;
+ view.setTitle("Yet Another Browser");
+ view.setFlags(Qt::Window | Qt::WindowTitleHint);
+ view.setResizeMode(QQuickView::SizeRootObjectToView);
+ view.setColor(Qt::black);
+ view.setSource(QUrl("qrc:///qml/Main.qml"));
+
+ QObject::connect(view.engine(), SIGNAL(quit()), &app, SLOT(quit()));
+
+#if defined(DESKTOP_BUILD)
+ view.show();
+ if (view.size().isEmpty())
+ view.setGeometry(0, 0, 800, 600);
+#else
+ view.showFullScreen();
+#endif
+
+ app.exec();
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/navigationhistoryproxymodel.cpp b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/navigationhistoryproxymodel.cpp
new file mode 100644
index 0000000..d574ae5
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/navigationhistoryproxymodel.cpp
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "navigationhistoryproxymodel.h"
+
+NavigationHistoryProxyModel::NavigationHistoryProxyModel(QObject *parent)
+ : QSortFilterProxyModel(parent)
+{
+
+}
+
+bool NavigationHistoryProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+
+ // Use UrlRole and TitleRole instead of DisplayRole
+ return (sourceModel()->data(index, Qt::UserRole + 1).toString().contains(filterRegExp())
+ || sourceModel()->data(index, Qt::UserRole + 2).toString().contains(filterRegExp()));
+}
+
+void NavigationHistoryProxyModel::setEnabled(bool enabled)
+{
+ if (dynamicSortFilter() == enabled)
+ return;
+ setDynamicSortFilter(enabled);
+ emit enabledChanged();
+}
+
+QString NavigationHistoryProxyModel::searchString() const
+{
+ return m_searchString;
+}
+
+void NavigationHistoryProxyModel::setSearchString(const QString &pattern)
+{
+ if (m_searchString == pattern)
+ return;
+
+ m_searchString = pattern;
+ setFilterRegExp(QRegExp(pattern, Qt::CaseInsensitive, QRegExp::FixedString));
+ emit searchStringChanged();
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/navigationhistoryproxymodel.h b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/navigationhistoryproxymodel.h
new file mode 100644
index 0000000..c2e3886
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/navigationhistoryproxymodel.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef NAVIGATIONHISTORYPROXYMODEL_H
+#define NAVIGATIONHISTORYPROXYMODEL_H
+
+#include <QObject>
+#include <QAbstractItemModel>
+#include <QSortFilterProxyModel>
+
+class NavigationHistoryProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QAbstractItemModel * target READ sourceModel WRITE setSourceModel)
+ Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY searchStringChanged)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+
+public:
+ explicit NavigationHistoryProxyModel(QObject *parent = 0);
+
+
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+
+ bool enabled() const { return dynamicSortFilter(); }
+ void setEnabled(bool enabled);
+
+ void setTarget(QAbstractItemModel *t);
+
+ QString searchString() const ;
+ void setSearchString(const QString &pattern);
+
+signals:
+ void searchStringChanged();
+ void enabledChanged();
+
+private:
+ QString m_searchString;
+};
+
+#endif // NAVIGATIONHISTORYPROXYMODEL_H
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/BrowserWindow.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/BrowserWindow.qml
new file mode 100644
index 0000000..bb8c231
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/BrowserWindow.qml
@@ -0,0 +1,473 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+import QtWebEngine 1.1
+
+import QtQuick.Controls 1.0
+import QtQuick.Controls.Styles 1.0
+import QtQuick.Layouts 1.0
+import QtQuick.Controls.Private 1.0
+import QtQuick.Dialogs 1.2
+
+import "assets"
+import WebBrowser 1.0
+import "Utils.js" as Utils
+
+Item {
+ id: browserWindow
+
+ property Item currentWebView: {
+ return tabView.get(tabView.currentIndex) ? tabView.get(tabView.currentIndex).item.webView : null
+ }
+
+ property string googleSearchQuery: "https://www.google.com/search?sourceid=qtbrowser&ie=UTF-8&q="
+
+ property int toolBarSize: 80
+ property string uiColor: settingsView.privateBrowsingEnabled ? "#3a4055" : "#09102b"
+
+ property string uiSeparatorColor: "#9d9faa"
+
+ property string toolBarSeparatorColor: "#9d9faa"
+
+ property string toolBarFillColor: settingsView.privateBrowsingEnabled ? "#3a4055" : "#09102b"
+
+ property string buttonPressedColor: "#41cd52"
+ property string emptyBackgroundColor: "#09102b"
+ property string uiHighlightColor: "#41cd52"
+ property string inactivePagerColor: "#bcbdbe"
+ property string textFieldStrokeColor: "#9d9faa"
+ property string placeholderColor: "#a0a1a2"
+ property string iconOverlayColor: "#0e202c"
+ property string iconStrokeColor: "#9d9faa"
+ property string defaultFontFamily: appFont
+
+ property int gridViewPageItemCount: 8
+ property int gridViewMaxBookmarks: 3 * gridViewPageItemCount
+ property int tabViewMaxTabs: 10
+ property int animationDuration: 200
+ property int velocityThreshold: 400
+ property int velocityY: 0
+ property real touchY: 0
+ property real touchReference: 0
+ property bool touchGesture: false
+
+ width: 1024
+ height: 600
+ visible: true
+
+ Action {
+ shortcut: "Ctrl+D"
+ onTriggered: {
+ downloadView.visible = !downloadView.visible
+ }
+ }
+
+ Action {
+ id: focus
+ shortcut: "Ctrl+L"
+ onTriggered: {
+ navigation.addressBar.forceActiveFocus();
+ navigation.addressBar.selectAll();
+ }
+ }
+ Action {
+ shortcut: "Ctrl+R"
+ onTriggered: {
+ if (currentWebView)
+ currentWebView.reload()
+ navigation.addressBar.forceActiveFocus()
+ }
+ }
+ Action {
+ id: newTabAction
+ shortcut: "Ctrl+T"
+ onTriggered: {
+ tabView.get(tabView.currentIndex).item.webView.takeSnapshot()
+ var tab = tabView.createEmptyTab()
+
+ if (!tab)
+ return
+
+ navigation.addressBar.selectAll();
+ tabView.makeCurrent(tabView.count - 1)
+ navigation.addressBar.forceActiveFocus()
+ }
+ }
+ Action {
+ shortcut: "Ctrl+W"
+ onTriggered: tabView.remove(tabView.currentIndex)
+ }
+
+ UIToolBar {
+ id: tabEditToolBar
+
+ source: "icons/Btn_Add.png"
+ indicator: tabView.count
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: navigation.top
+ }
+
+ visible: opacity != 0.0
+ opacity: tabView.viewState == "list" ? 1.0 : 0.0
+ onDoneClicked: tabView.viewState = "page"
+ onOptionClicked: newTabAction.trigger()
+ }
+
+ UIToolBar {
+ id: settingsToolBar
+ z: 5
+ title: qsTr("Settings")
+ visible: opacity != 0.0
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: navigation.top
+ }
+
+ onDoneClicked: {
+ settingsView.save()
+ settingsView.state = "disabled"
+ }
+ }
+
+ UIToolBar {
+ id: fullScreenBar
+ z: 6
+ title: qsTr("Leave Full Screen Mode")
+ visible: opacity != 0.0
+ opacity: tabView.viewState == "fullscreen" ? 1.0 : 0.0
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: navigation.top
+ }
+ onDoneClicked: {
+ navigation.webView.triggerWebAction(WebEngineView.ExitFullScreen);
+ }
+ }
+
+ NavigationBar {
+ id: navigation
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ }
+ Rectangle{
+ anchors.bottom: navigation.bottom
+ anchors.left: navigation.left
+ anchors.right: navigation.right
+ height: 2
+ color: "#9d9faa"
+ }
+
+ PageView {
+ id: tabView
+ interactive: {
+ if (sslDialog.visible || homeScreen.state != "disabled" || urlDropDown.state == "enabled" || settingsView.state == "enabled")
+ return false
+ return true
+ }
+
+ anchors {
+ top: navigation.bottom
+ left: parent.left
+ right: parent.right
+ }
+
+ height: inputPanel.y
+
+ Component.onCompleted: {
+ var tab = createEmptyTab()
+
+ if (!tab)
+ return
+
+ navigation.webView = tab.webView
+ var url = AppEngine.initialUrl
+
+ navigation.load();
+ }
+ onCurrentIndexChanged: {
+ if (!tabView.get(tabView.currentIndex))
+ return
+ navigation.webView = tabView.get(tabView.currentIndex).item.webView
+ }
+ }
+
+ QtObject{
+ id: acceptedCertificates
+
+ property var acceptedUrls : []
+
+ function shouldAutoAccept(certificateError){
+ var domain = AppEngine.domainFromString(certificateError.url)
+ return acceptedUrls.indexOf(domain) >= 0
+ }
+ }
+
+ MessageDialog {
+ id: sslDialog
+
+ property var certErrors: []
+ property var currentError: null
+ visible: certErrors.length > 0
+ icon: StandardIcon.Warning
+ standardButtons: StandardButton.No | StandardButton.Yes
+ title: "Server's certificate not trusted"
+ text: "Do you wish to continue?"
+ detailedText: "If you wish so, you may continue with an unverified certificate. " +
+ "Accepting an unverified certificate means " +
+ "you may not be connected with the host you tried to connect to.\n" +
+ "Do you wish to override the security check and continue?"
+ onYes: {
+ var cert = certErrors.shift()
+ var domain = AppEngine.domainFromString(cert.url)
+ acceptedCertificates.acceptedUrls.push(domain)
+ cert.ignoreCertificateError()
+ presentError()
+ }
+ onNo: reject()
+ onRejected: reject()
+
+ function reject(){
+ certErrors.shift().rejectCertificate()
+ presentError()
+ }
+ function enqueue(error){
+ currentError = error
+ certErrors.push(error)
+ presentError()
+ }
+ function presentError(){
+ informativeText = "SSL error from URL\n\n" + currentError.url + "\n\n" + currentError.description + "\n"
+ }
+ }
+
+ Rectangle {
+ id: urlDropDown
+ color: "white"
+ visible: navigation.visible
+
+ property string searchString: navigation.addressBar.text
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: navigation.bottom
+ }
+
+ state: "disabled"
+
+ states: [
+ State {
+ name: "enabled"
+ PropertyChanges {
+ target: urlDropDown
+ height: browserWindow.height - toolBarSize - 3
+ }
+ },
+ State {
+ name: "disabled"
+ PropertyChanges {
+ target: urlDropDown
+ height: 0
+ }
+ }
+ ]
+
+ Rectangle {
+ anchors.fill: parent
+ color: emptyBackgroundColor
+ }
+
+ SearchProxyModel {
+ id: proxy
+ target: navigation.webView.navigationHistory.items
+ searchString: urlDropDown.searchString
+ enabled: urlDropDown.state == "enabled"
+ }
+
+ ListView {
+ id: historyList
+ property int remainingHeight: Math.min((historyList.count + 1) * toolBarSize, inputPanel.y - toolBarSize - 3)
+ model: proxy
+ clip: true
+ boundsBehavior: Flickable.StopAtBounds
+ footerPositioning: ListView.InlineFooter
+ visible: urlDropDown.state == "enabled"
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+ height: remainingHeight
+ delegate: Rectangle {
+ id: wrapper
+ width: historyList.width
+ height: toolBarSize
+ color: "#09102b"
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (!url)
+ return
+ navigation.webView.url = url
+ navigation.webView.forceActiveFocus()
+ }
+ }
+
+ Column {
+ width: parent.width - 60
+ height: parent.height
+ anchors {
+ verticalCenter: parent.verticalCenter
+ horizontalCenter: parent.horizontalCenter
+ }
+ Text {
+ property string highlightTitle: title ? title : ""
+ height: wrapper.height / 2
+ width: parent.width
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignBottom
+ anchors{
+ leftMargin: 30
+ rightMargin: 30
+ }
+ id: titleLabel
+ font.family: defaultFontFamily
+ font.pixelSize: 23
+ color: "white"
+ text: Utils.highlight(highlightTitle, urlDropDown.searchString)
+ }
+ Text {
+ property string highlightUrl: url ? url : ""
+ height: wrapper.height / 2 - 1
+ width: parent.width
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignTop
+ font.family: defaultFontFamily
+ font.pixelSize: 23
+ color: "white" //uiColor
+ text: Utils.highlight(highlightUrl, urlDropDown.searchString)
+ }
+ Rectangle {
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: historyList.width
+ height: 1
+ color: iconStrokeColor
+ }
+ }
+ }
+ footer: Rectangle {
+ z: 5
+ width: historyList.width
+ height: toolBarSize
+ color: "#09102b"
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ var string = urlDropDown.searchString
+ var constructedUrl = ""
+ if (AppEngine.isUrl(string)) {
+ constructedUrl = AppEngine.fromUserInput(string)
+ } else {
+ constructedUrl = AppEngine.fromUserInput(googleSearchQuery + string)
+ }
+ navigation.webView.url = constructedUrl
+ navigation.webView.forceActiveFocus()
+ }
+ }
+ Row {
+ height: parent.height
+ Rectangle {
+ id: searchIcon
+ height: parent.height
+ width: height
+ color: "transparent"
+ Image {
+ anchors.centerIn: parent
+ source: "assets/icons/Btn_Search.png"
+ }
+ }
+ Text {
+ id: searchText
+ height: parent.height
+ width: historyList.width - searchIcon.width - 30
+ elide: Text.ElideRight
+ text: urlDropDown.searchString
+ verticalAlignment: Text.AlignVCenter
+ font.family: defaultFontFamily
+ font.pixelSize: 23
+ color: "white"
+ }
+ }
+ }
+ }
+
+ transitions: Transition {
+ PropertyAnimation { property: "height"; duration: animationDuration; easing.type : Easing.InSine }
+ }
+ }
+
+ HomeScreen {
+ id: homeScreen
+ height: parent.height - toolBarSize
+ anchors {
+ top: navigation.bottom
+ left: parent.left
+ right: parent.right
+ }
+ }
+
+ SettingsView {
+ id: settingsView
+ height: parent.height - toolBarSize
+ anchors {
+ top: navigation.bottom
+ left: parent.left
+ right: parent.right
+ }
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/CustomSwitch.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/CustomSwitch.qml
new file mode 100644
index 0000000..7f8f674
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/CustomSwitch.qml
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Device Utilities module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** 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 or (at your option) 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.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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.0
+import QtDeviceUtilities.QtButtonImageProvider 1.0
+import QtQuick.Controls 2.1
+
+Switch {
+ id: control
+
+ property alias indicatorWidth: indicatorImg.width
+ property alias indicatorHeight: indicatorImg.height
+ property string switchTextColor: "#3b4155"
+
+ indicator: Image {
+ id: indicatorImg
+ width: 200
+ height: 75
+ sourceSize: Qt.size(width, height)
+ anchors.horizontalCenter: control.horizontalCenter
+ y: parent.height / 2 - height / 2
+ source: "image://QtButton/10/#848895/transparent"
+ Text {
+ id: offText
+ anchors.left: parent.left
+ anchors.leftMargin: parent.width * 0.075
+ anchors.verticalCenter: parent.verticalCenter
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ fontSizeMode: Text.Fit
+ minimumPixelSize: 1
+ font.pixelSize: parent.height * 0.55
+ color: switchTextColor
+ text: "OFF"
+ font.family: defaultFontFamily
+ }
+ Text {
+ id: onText
+ anchors.right: parent.right
+ anchors.rightMargin: parent.width * 0.1
+ anchors.verticalCenter: parent.verticalCenter
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ fontSizeMode: Text.Fit
+ minimumPixelSize: 1
+ font.pixelSize: parent.height * 0.55
+ color: switchTextColor
+ text: "ON"
+ font.family: defaultFontFamily
+ }
+
+ Binding {
+ target: qtHandle
+ property: "x"
+ value: control.checked ? indicatorImg.width - qtHandle.width - indicatorImg.width * 0.025 : indicatorImg.width * 0.025
+ when: !mousearea.drag.active
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: control.checked = !control.checked
+ }
+
+ QtButton {
+ id: qtHandle
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width * 0.475
+ height: parent.height * 0.9
+ fillColor: control.checked ? buttonPressedColor : toolBarSeparatorColor
+ text: control.checked ? "ON" : "OFF"
+ borderColor: "transparent"
+ Behavior on x {
+ NumberAnimation { duration: 50 }
+ }
+
+ MouseArea {
+ id: mousearea
+ anchors.fill: parent
+ drag.target: qtHandle
+ drag.axis: Drag.XAxis
+ drag.minimumX: indicatorImg.width * 0.005
+ drag.maximumX: indicatorImg.width - width - indicatorImg.width * 0.005
+
+ onClicked: {
+ control.checked = !control.checked
+ }
+
+ onReleased: {
+ if (qtHandle.x > indicatorImg.width / 5 ) {
+ control.checked = true
+ } else {
+ control.checked = false
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/FeaturePermissionBar.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/FeaturePermissionBar.qml
new file mode 100644
index 0000000..8595724
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/FeaturePermissionBar.qml
@@ -0,0 +1,140 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.1
+import QtQuick.Controls 1.0
+import QtWebEngine 1.1
+import QtQuick.Layouts 1.0
+
+import "assets"
+
+Rectangle {
+ property var requestedFeature;
+ property url securityOrigin;
+ property WebEngineView view;
+
+ id: permissionBar
+ visible: false
+ height: 50
+
+ onRequestedFeatureChanged: {
+ message.text = securityOrigin + " wants to access " + message.textForFeature(requestedFeature);
+ }
+
+
+ RowLayout {
+ spacing: 0
+ anchors {
+ fill: permissionBar
+ }
+ Rectangle {
+ color: uiColor
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ Layout.fillWidth: true
+
+ Text {
+ id: message
+ width: parent.width
+ elide: Text.ElideRight
+ horizontalAlignment: Text.AlignHCenter
+ anchors {
+ leftMargin: 15
+ rightMargin: 15
+ verticalCenter: parent.verticalCenter
+ horizontalCenter: parent.horizontalCenter
+ }
+ font.family: defaultFontFamily
+ font.pixelSize: 20
+ color: "white"
+ function textForFeature(feature) {
+ if (feature === WebEngineView.MediaAudioCapture)
+ return "your microphone"
+ if (feature === WebEngineView.MediaVideoCapture)
+ return "your camera"
+ if (feature === WebEngineView.MediaAudioVideoCapture)
+ return "your camera and microphone"
+ if (feature === WebEngineView.Geolocation)
+ return "your position"
+ }
+ }
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+
+ UIButton {
+ id: acceptButton
+ implicitHeight: permissionBar.height
+ implicitWidth: toolBarSize
+ buttonText: "Accept"
+ textSize: 18
+ Layout.alignment: Qt.AlignRight
+ onClicked: {
+ view.grantFeaturePermission(securityOrigin, requestedFeature, true);
+ permissionBar.visible = false;
+ }
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+
+ UIButton {
+ buttonText: "Deny"
+ textSize: 18
+ implicitHeight: permissionBar.height
+ implicitWidth: toolBarSize
+ Layout.alignment: Qt.AlignRight
+ onClicked: {
+ view.grantFeaturePermission(securityOrigin, requestedFeature, false);
+ permissionBar.visible = false
+ }
+ }
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/HomeScreen.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/HomeScreen.qml
new file mode 100644
index 0000000..9d29729
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/HomeScreen.qml
@@ -0,0 +1,603 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+import WebBrowser 1.0
+import "assets"
+
+Rectangle {
+ id: homeScreen
+ property int padding: 60
+ property int cellSize: width / 5 - padding
+ property alias messageBox: messageBox
+ property alias count: gridView.count
+ property alias currentIndex: gridView.currentIndex
+ color: "#09102b"
+ function set(i) {
+ var p = (i - i % gridViewPageItemCount) / gridViewPageItemCount
+ gridView.contentX = p * gridView.page
+ }
+
+ state: "enabled"
+ onStateChanged: {
+ if (state == "enabled" && !gridView.count)
+ messageBox.state = "empty"
+ }
+
+ signal add(string title, string url, string iconUrl, string fallbackColor)
+ onAdd: {
+ if (listModel.count === gridViewMaxBookmarks) {
+ navigation.refresh()
+ messageBox.state = "full"
+ state = "enabled"
+ homeScreen.forceActiveFocus()
+ return
+ }
+ var icon = url.indexOf("qt.io") != -1 ? "assets/icons/qt.png" : iconUrl
+ var element = { "title": title, "url": url, "iconUrl": icon, "fallbackColor": fallbackColor }
+ listModel.append(element)
+ set(listModel.count - 1)
+ }
+
+ signal remove(string url, int idx)
+ onRemove: {
+ var index = idx < 0 ? contains(url) : idx
+ if (index < 0)
+ return
+
+ listModel.remove(index)
+ gridView.forceLayout()
+ navigation.refresh()
+ if (!listModel.count)
+ messageBox.state = "empty"
+ }
+
+ function get(index) {
+ return listModel.get(index)
+ }
+
+ function contains(url) {
+ for (var idx = 0; idx < listModel.count; ++idx) {
+ if (listModel.get(idx).url === url)
+ return idx;
+ }
+ return -1;
+ }
+
+ states: [
+ State {
+ name: "enabled"
+ AnchorChanges {
+ target: homeScreen
+ anchors.top: navigation.bottom
+ }
+ },
+ State {
+ name: "disabled"
+ AnchorChanges {
+ target: homeScreen
+ anchors.top: homeScreen.parent.bottom
+ }
+ },
+ State {
+ name: "edit"
+ }
+ ]
+
+ transitions: Transition {
+ AnchorAnimation { duration: animationDuration; easing.type : Easing.InSine }
+ }
+
+ ListModel {
+ id: listModel
+ property string defaultBookmarks: "[{\"fallbackColor\":\"#46a2da\",\"iconUrl\":\"assets/icons/qt.png\",\"title\":\"Qt - Home\",\"url\":\"http://www.qt.io/\"},{\"fallbackColor\":\"#18394c\",\"iconUrl\":\"http://www.topgear.com/sites/all/themes/custom/tg/apple-touch-icon-144x144.png\",\"title\":\"Top Gear\",\"url\":\"http://www.topgear.com/\"},{\"fallbackColor\":\"#46a2da\",\"iconUrl\":\"https://duckduckgo.com/assets/icons/meta/DDG-iOS-icon_152x152.png\",\"title\":\"DuckDuckGo\",\"url\":\"https://duckduckgo.com/\"},{\"fallbackColor\":\"#ff8c0a\",\"iconUrl\":\"http://www.blogsmithmedia.com/www.engadget.com/media/favicon-160x160.png\",\"title\":\"Engadget | Technology News, Advice and Features\",\"url\":\"http://www.engadget.com/\"},{\"fallbackColor\":\"#ff8c0a\",\"iconUrl\":\"https://www.openstreetmap.org/assets/favicon-194x194-32cdac24b02b88e09f0639bb92c760b2.png\",\"title\":\"OpenStreetMap\",\"url\":\"https://www.openstreetmap.org/\"},{\"fallbackColor\":\"#5caa15\",\"iconUrl\":\"http://www.redditstatic.com/icon.png\",\"title\":\"reddit: the front page of the internet\",\"url\":\"http://www.reddit.com/\"}]"
+
+ Component.onCompleted: {
+ listModel.clear()
+ var string = AppEngine.restoreSetting("bookmarks", defaultBookmarks)
+ if (!string)
+ return
+ var list = JSON.parse(string)
+ for (var i = 0; i < list.length; ++i) {
+ listModel.append(list[i])
+ }
+ navigation.refresh()
+ }
+ Component.onDestruction: {
+ var list = []
+ for (var i = 0; i < listModel.count; ++i) {
+ list[i] = listModel.get(i)
+ }
+ AppEngine.saveSetting("bookmarks", JSON.stringify(list))
+ }
+ }
+
+ GridView {
+ id: gridView
+
+ onCountChanged: {
+ if (!count)
+ messageBox.state = "empty"
+ else
+ messageBox.state = "disabled"
+ }
+
+ property real dragStart: 0
+ property real page: 4 * cellWidth
+
+ anchors.fill: parent
+ model: listModel
+ cellWidth: homeScreen.cellSize + homeScreen.padding
+ cellHeight: cellWidth
+ flow: GridView.FlowTopToBottom
+ boundsBehavior: Flickable.StopAtBounds
+ maximumFlickVelocity: 0
+ contentHeight: parent.height
+
+ MouseArea {
+ z: -1
+ enabled: homeScreen.state == "edit"
+ anchors.fill: parent
+ onClicked: homeScreen.state = "enabled"
+ }
+
+ rightMargin: {
+ var margin = (parent.width - 4 * gridView.cellWidth - homeScreen.padding) / 2
+ var padding = gridView.page - Math.round(gridView.count % gridViewPageItemCount / 2) * gridView.cellWidth
+
+ if (padding == gridView.page)
+ return margin
+
+ return margin + padding
+ }
+
+ anchors {
+ topMargin: toolBarSize
+ leftMargin: (parent.width - 4 * gridView.cellWidth + homeScreen.padding) / 2
+ }
+
+ Behavior on contentX {
+ NumberAnimation { duration: 1.5 * animationDuration; easing.type : Easing.InSine}
+ }
+
+ function snapToPage() {
+ if (dragging) {
+ dragStart = contentX
+ return
+ }
+ if (dragStart == 2 * page && contentX < 2 * page) {
+ contentX = page
+ return
+ }
+ if (dragStart == page) {
+ if (contentX < page) {
+ contentX = 0
+ return
+ }
+ if (page < contentX) {
+ contentX = 2 * page
+ return
+ }
+ }
+ if (dragStart == 0 && 0 < contentX) {
+ contentX = page
+ return
+ }
+ contentX = 0
+ }
+
+ onDraggingChanged: snapToPage()
+ delegate: Rectangle {
+ id: square
+ property string iconColor: "#f6f6f6"
+ width: homeScreen.cellSize
+ height: width
+ border.color: iconStrokeColor
+ border.width: 1
+
+ Rectangle {
+ id: bg
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ top: parent.top
+ margins: 1
+ }
+ state: "fallback"
+ width: square.width - 2
+ height: width
+ states: [
+ State {
+ name: "fallback"
+ PropertyChanges {
+ target: square
+ color: fallbackColor
+ }
+ PropertyChanges {
+ target: bg
+ color: square.color
+ }
+ },
+ State {
+ name: "normal"
+ PropertyChanges {
+ target: square
+ color: iconColor
+ }
+ PropertyChanges {
+ target: bg
+ color: square.color
+ }
+ }
+ ]
+
+ Image {
+ id: icon
+ smooth: true
+ anchors {
+ top: parent.top
+ horizontalCenter: parent.horizontalCenter
+ topMargin: width < bg.width ? 15 : 0
+ }
+ width: {
+ if (!icon.sourceSize.width)
+ return 0
+ if (icon.sourceSize.width < 100)
+ return 32
+
+ return bg.width
+ }
+ height: width
+ source: iconUrl
+ onStatusChanged: {
+ switch (status) {
+ case Image.Null:
+ case Image.Loading:
+ case Image.Error:
+ bg.state = "fallback"
+ break
+ case Image.Ready:
+ bg.state = "normal"
+ break
+ }
+ }
+ }
+ Text {
+ function cleanup(string) {
+ var t = string.replace("-", " ")
+ .replace("|", " ").replace(",", " ")
+ .replace(/\s\s+/g, "\n")
+ return t
+ }
+
+ visible: icon.width != bg.width
+ text: cleanup(title)
+ font.family: defaultFontFamily
+ font.pixelSize: 18
+ color: bg.state == "fallback" ? "white" : "black"
+ anchors {
+ top: icon.bottom
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ leftMargin: 15
+ rightMargin: 15
+ bottomMargin: 15
+ }
+ maximumLineCount: 3
+ elide: Text.ElideRight
+ wrapMode: Text.Wrap
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+
+ Rectangle {
+ id: overlay
+ visible: opacity != 0.0
+ anchors.fill: parent
+ color: iconOverlayColor
+ opacity: {
+ if (iconMouse.pressed) {
+ if (homeScreen.state != "edit")
+ return 0.1
+ return 0.4
+ }
+ if (homeScreen.state == "edit")
+ return 0.3
+ return 0.0
+ }
+ }
+ MouseArea {
+ id: iconMouse
+ anchors.fill: parent
+ onPressAndHold: {
+ if (homeScreen.state == "edit") {
+ homeScreen.state = "enabled"
+ return
+ }
+ homeScreen.state = "edit"
+ }
+ onClicked: {
+ if (homeScreen.state == "edit") {
+ homeScreen.state = "enabled"
+ return
+ }
+ navigation.load(url)
+ }
+ }
+ Rectangle {
+ enabled: homeScreen.state == "edit"
+ opacity: enabled ? 1.0 : 0.0
+ width: image.sourceSize.width
+ height: image.sourceSize.height - 2
+ radius: width / 2
+ color: iconOverlayColor
+ anchors {
+ horizontalCenter: parent.right
+ verticalCenter: parent.top
+ }
+ Image {
+ id: image
+ opacity: {
+ if (deleteButton.pressed)
+ return 0.70
+ return 1.0
+ }
+ anchors {
+ top: parent.top
+ left: parent.left
+ }
+ source: "assets/icons/Btn_Delete.png"
+ MouseArea {
+ id: deleteButton
+ anchors.fill: parent
+ onClicked: {
+ mouse.accepted = true
+ remove(url, index)
+ }
+ }
+ }
+ Behavior on opacity {
+ NumberAnimation { duration: animationDuration }
+ }
+ }
+ }
+ }
+ Rectangle {
+ width: homeScreen.cellSize - homeScreen.padding / 2 - 10
+ anchors {
+ left: parent.left
+ top: parent.top
+ bottom: parent.bottom
+ }
+ MouseArea {
+ enabled: homeScreen.state == "edit"
+ anchors.fill: parent
+ onClicked: homeScreen.state = "enabled"
+ }
+ color: "#09102b"
+ }
+ Rectangle {
+ width: homeScreen.cellSize - homeScreen.padding / 2 - 10
+ anchors {
+ right: parent.right
+ top: parent.top
+ bottom: parent.bottom
+ }
+ MouseArea {
+ enabled: homeScreen.state == "edit"
+ anchors.fill: parent
+ onClicked: homeScreen.state = "enabled"
+ }
+ color: "#09102b"
+ }
+ Row {
+ id: pageIndicator
+ spacing: 20
+ anchors {
+ bottomMargin: 40
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+ Repeater {
+ model: {
+ var c = gridView.count % gridViewPageItemCount
+ if (c > 0)
+ c = 1
+ return Math.floor(gridView.count / gridViewPageItemCount) + c
+ }
+ delegate: Rectangle {
+ property bool active: index * gridView.page <= gridView.contentX && gridView.contentX < (index + 1) * gridView.page
+ width: 10
+ height: width
+ radius: width / 2
+ color: !active ? inactivePagerColor : uiColor
+ anchors.verticalCenter: parent.verticalCenter
+ MouseArea {
+ anchors.fill: parent
+ onClicked: gridView.contentX = index * gridView.page
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: messageBox
+ color: "white"
+ anchors.fill: parent
+
+ Rectangle {
+ id: error
+ visible: messageBox.state != "empty"
+ height: childrenRect.height
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ topMargin: 50
+ }
+ Image {
+ id: errorIcon
+ source: "assets/icons/Error_Icon.png"
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ top: parent.top
+ }
+ }
+ Text {
+ anchors {
+ topMargin: 30
+ top: errorIcon.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+ font.family: defaultFontFamily
+ font.pixelSize: message.font.pixelSize
+ text: "Oops!..."
+ color: iconOverlayColor
+ }
+ }
+
+ Text {
+ id: message
+ anchors {
+ top: error.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+ color: iconOverlayColor
+ font.family: defaultFontFamily
+ font.pixelSize: 28
+ verticalAlignment: Text.AlignTop
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Rectangle {
+ color: parent.color
+ anchors {
+ top: message.bottom
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ bottomMargin: 70
+ }
+ UIButton {
+ color: uiColor
+ implicitWidth: 180
+ implicitHeight: 70
+ buttonText: "OK"
+ visible: messageBox.state != "empty"
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ bottom: parent.bottom
+ }
+ onClicked: {
+ if (messageBox.state == "tabsfull") {
+ homeScreen.state = "disabled"
+ tabView.viewState = "list"
+ return
+ }
+ if (messageBox.state == "full") {
+ messageBox.state = "disabled"
+ homeScreen.state = "edit"
+ return
+ }
+ }
+ }
+ }
+
+ state: "disabled"
+
+ states: [
+ State {
+ name: "disabled"
+ PropertyChanges {
+ target: messageBox
+ visible: false
+ }
+ },
+ State {
+ name: "empty"
+ PropertyChanges {
+ target: message
+ text: qsTr("No bookmarks have been saved so far.")
+ }
+ PropertyChanges {
+ target: messageBox
+ color: emptyBackgroundColor
+ visible: true
+ }
+ PropertyChanges {
+ target: error
+ anchors.topMargin: 30
+ }
+ PropertyChanges {
+ target: navigation
+ state: "enabled"
+ }
+ },
+ State {
+ name: "full"
+ PropertyChanges {
+ target: message
+ text: qsTr("24 bookmarks is the maximum limit.\nTo bookmark a new page you must delete a bookmark first.")
+ }
+ PropertyChanges {
+ target: messageBox
+ visible: true
+ }
+ PropertyChanges {
+ target: navigation
+ state: "enabled"
+ }
+ },
+ State {
+ name: "tabsfull"
+ PropertyChanges {
+ target: message
+ text: qsTr("10 open tabs is the maximum limit.\nTo open a new tab you must close another one first.")
+ }
+ PropertyChanges {
+ target: messageBox
+ visible: true
+ }
+ PropertyChanges {
+ target: navigation
+ state: "enabled"
+ }
+ }
+ ]
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Keyboard.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Keyboard.qml
new file mode 100644
index 0000000..13ab64e
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Keyboard.qml
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+import QtQuick.Enterprise.VirtualKeyboard 2.0
+
+InputPanel {
+ id: inputPanel
+ property int windowHeight: 0
+ property int animationDuration: 0
+
+ y: windowHeight
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ states: State {
+ name: "visible"
+ when: Qt.inputMethod.visible
+ PropertyChanges {
+ target: inputPanel
+ y: windowHeight - inputPanel.height
+ }
+ }
+ transitions: Transition {
+ from: ""
+ to: "visible"
+ reversible: true
+ NumberAnimation {
+ properties: "y"
+ duration: animationDuration
+ easing.type: Easing.InOutQuad
+ }
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Main.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Main.qml
new file mode 100644
index 0000000..3f01ecc
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Main.qml
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+
+Item {
+ BrowserWindow{
+ id: root
+ anchors.fill: parent
+ Keyboard{
+ id: inputPanel
+ windowHeight: root.height
+ animationDuration: root.animationDuration
+ }
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/MockTouchPoint.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/MockTouchPoint.qml
new file mode 100644
index 0000000..f7fa1b1
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/MockTouchPoint.qml
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+Item {
+ id: mockTouchPoint
+
+ property bool pressed: false
+ property int pointId: 0
+
+ Image {
+ source: "assets/icons/touchpoint.png"
+ x: -(width / 2)
+ y: -(height / 2)
+ height: parent.height
+ width: parent.width
+ opacity: parent.pressed ? 0.6 : 0.0
+
+ Behavior on opacity {
+ NumberAnimation { duration: 200 }
+ }
+
+ Text {
+ text: mockTouchPoint.pointId
+ anchors.centerIn: parent
+ }
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/NavigationBar.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/NavigationBar.qml
new file mode 100644
index 0000000..19efa0d
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/NavigationBar.qml
@@ -0,0 +1,453 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Layouts 1.2
+import WebBrowser 1.0
+
+import "assets"
+
+ToolBar {
+ id: root
+
+ property alias addressBar: urlBar
+ property Item webView: null
+
+ onWebViewChanged: {
+
+ }
+
+ visible: opacity != 0.0
+ opacity: tabView.viewState == "page" ? 1.0 : 0.0
+
+ function load(url) {
+ if (url)
+ webView.url = url
+ homeScreen.state = "disabled"
+ }
+
+ function refresh() {
+ if (urlBar.text == "")
+ bookmarksButton.bookmarked = false
+ else
+ bookmarksButton.bookmarked = homeScreen.contains(urlBar.text) !== -1
+ }
+
+ state: "enabled"
+
+ style: ToolBarStyle {
+ background: Rectangle {
+ color: uiColor
+ implicitHeight: toolBarSize + 3
+ }
+ padding {
+ left: 0
+ right: 0
+ top: 0
+ bottom: 0
+ }
+ }
+
+ Behavior on y {
+ NumberAnimation { duration: animationDuration }
+ }
+
+ states: [
+ State {
+ name: "enabled"
+ PropertyChanges {
+ target: root
+ y: 0
+ }
+ },
+ State {
+ name: "tracking"
+ PropertyChanges {
+ target: root
+ y: {
+ var diff = touchReference - touchY
+
+ if (velocityY > velocityThreshold) {
+ if (diff > 0)
+ return -root.height
+ else
+ return 0
+ }
+
+ if (!touchGesture || diff == 0) {
+ if (y < -root.height / 2)
+ return -root.height
+ else
+ return 0
+ }
+
+ if (diff > root.height)
+ return -root.height
+
+ if (diff > 0) {
+ if (y == -root.height)
+ return -root.height
+ return -diff
+ }
+
+ // diff < 0
+
+ if (y == 0)
+ return 0
+
+ diff = Math.abs(diff)
+ if (diff >= root.height)
+ return 0
+
+ return -root.height + diff
+ }
+ }
+ },
+ State {
+ name: "disabled"
+ PropertyChanges {
+ target: root
+ y: -root.height
+ }
+ }
+ ]
+
+ RowLayout {
+ height: toolBarSize
+ anchors {
+ top: parent.top
+ right: parent.right
+ left: parent.left
+ }
+ spacing: 0
+
+ UIButton {
+ id: backButton
+ source: "icons/Btn_Back.png"
+ color: uiColor
+ highlightColor: buttonPressedColor
+ onClicked: webView.goBack()
+ enabled: webView && webView.canGoBack
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ UIButton {
+ id: forwardButton
+ source: "icons/Btn_Forward.png"
+ color: uiColor
+ highlightColor: buttonPressedColor
+ onClicked: webView.goForward()
+ enabled: webView && webView.canGoForward
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ Rectangle {
+ Layout.fillWidth: true
+ implicitWidth: 10
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiColor
+ }
+ TextField {
+ id: urlBar
+ Layout.fillWidth: true
+ text: webView ? webView.url : ""
+ activeFocusOnPress: true
+ inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase | Qt.ImhPreferLowercase
+ placeholderText: qsTr("Search or type a URL")
+
+ onActiveFocusChanged: {
+ if (activeFocus) {
+ urlBar.selectAll()
+ root.state = "enabled"
+ homeScreen.state = "disabled"
+ urlDropDown.state = "enabled"
+ } else {
+ urlDropDown.state = "disabled"
+ root.state = "tracking"
+ }
+ }
+
+ UIButton {
+ id: reloadButton
+ state: cancelButton.visible ? "edit" : "load"
+ states: [
+ State {
+ name: "load"
+ PropertyChanges {
+ target: reloadButton
+ source: webView && webView.loading ? "icons/Btn_Clear.png" : "icons/Btn_Reload.png"
+ height: 54
+ }
+ },
+ State {
+ name: "edit"
+ PropertyChanges {
+ target: reloadButton
+ source: "icons/Btn_Clear.png"
+ height: 45
+ visible: urlBar.text != ""
+ }
+ }
+ ]
+ height: 54
+ width: height
+ color: "transparent"
+ highlightColor: "#eeeeee"
+ radius: width / 2
+ anchors {
+ rightMargin: 1
+ right: parent.right
+ verticalCenter: addressBar.verticalCenter;
+ }
+ onClicked: {
+ if (state == "load") {
+ webView.loading ? webView.stop() : webView.reload()
+ webView.forceActiveFocus()
+ return
+ }
+ urlBar.selectAll()
+ urlBar.remove(urlBar.selectionStart, urlBar.selectionEnd)
+ }
+ }
+ style: TextFieldStyle {
+ textColor: "white"
+ font.family: defaultFontFamily
+ font.pixelSize: 28
+ selectionColor: uiHighlightColor
+ selectedTextColor: "white"
+ placeholderTextColor: placeholderColor
+ background: Rectangle {
+ implicitWidth: 514
+ implicitHeight: 56
+ border.color: textFieldStrokeColor
+ color: "#09102b"
+ border.width: 2
+ }
+ padding {
+ left: 15
+ right: reloadButton.width
+ }
+ }
+ onAccepted: {
+ webView.url = AppEngine.fromUserInput(text)
+ homeScreen.state = "disabled"
+ tabView.viewState = "page"
+ }
+
+ onTextChanged: refresh()
+ onEditingFinished: {
+ selectAll()
+ webView.forceActiveFocus()
+ }
+ }
+ Rectangle {
+ visible: !cancelButton.visible
+ Layout.fillWidth: true
+ implicitWidth: 10
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiColor
+ }
+
+ UIButton {
+ id: cancelButton
+ color: uiColor
+ visible: urlDropDown.state === "enabled"
+ highlightColor: buttonPressedColor
+ Text {
+ color: "white"
+ anchors.centerIn: parent
+ text: "Cancel"
+ font.family: defaultFontFamily
+ font.pixelSize: 28
+ }
+ implicitWidth: 120
+ onClicked: {
+ urlDropDown.state = "disabled"
+ webView.forceActiveFocus()
+ }
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ UIButton {
+ id: homeButton
+ source: "icons/Btn_Home.png"
+ color: uiColor
+ highlightColor: buttonPressedColor
+ onClicked: {
+ if (homeScreen.state == "disabled" || homeScreen.state == "edit") {
+ homeScreen.messageBox.state = "disabled"
+ homeScreen.state = "enabled"
+ homeScreen.forceActiveFocus()
+ } else if (homeScreen.state != "disabled") {
+ homeScreen.state = "disabled"
+ }
+ }
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ UIButton {
+ id: pageViewButton
+ source: "icons/Btn_Tabs.png"
+ color: uiColor
+ highlightColor: buttonPressedColor
+ onClicked: {
+ if (tabView.viewState == "list") {
+ tabView.viewState = "page"
+ } else {
+ tabView.get(tabView.currentIndex).item.webView.takeSnapshot()
+ homeScreen.state = "disabled"
+ tabView.viewState = "list"
+ }
+ }
+ Text {
+ anchors {
+ centerIn: parent
+ verticalCenterOffset: 4
+ }
+
+ text: tabView.count
+ font.family: defaultFontFamily
+ font.pixelSize: 16
+ font.weight: Font.DemiBold
+ color: "white"
+ }
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ UIButton {
+ id: bookmarksButton
+ color: uiColor
+ highlightColor: buttonPressedColor
+ enabled: urlBar.text != "" && !settingsView.privateBrowsingEnabled
+ property bool bookmarked: false
+ source: bookmarked ? "icons/Btn_Bookmark_Checked.png" : "icons/Btn_Bookmarks.png"
+ onClicked: {
+ if (!webView)
+ return
+ var icon = webView.loading ? "" : webView.icon
+ var idx = homeScreen.contains(webView.url.toString())
+ if (idx !== -1) {
+ homeScreen.remove("", idx)
+ return
+ }
+ var count = homeScreen.count
+ homeScreen.add(webView.title, webView.url, icon, AppEngine.fallbackColor())
+ if (count < homeScreen.count)
+ bookmarked = true
+ }
+ Component.onCompleted: refresh()
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ UIButton {
+ id: settingsButton
+ source: "icons/Btn_Settings.png"
+ color: uiColor
+ highlightColor: buttonPressedColor
+ onClicked: {
+ settingsView.state = "enabled"
+ }
+ }
+ }
+ ProgressBar {
+ id: progressBar
+ height: 3
+ anchors {
+ left: parent.left
+ bottom: parent.bottom
+ right: parent.right
+ leftMargin: -10
+ rightMargin: -10
+ }
+ style: ProgressBarStyle {
+ background: Rectangle {
+ height: 3
+ color: emptyBackgroundColor
+ }
+ progress: Rectangle {
+ //color: settingsView.privateBrowsingEnabled ? "#46a2da" : "#317198"
+ color: "#41cd52"
+ }
+ }
+ minimumValue: 0
+ maximumValue: 100
+ value: (webView && webView.loadProgress < 100) ? webView.loadProgress : 0
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/PageView.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/PageView.qml
new file mode 100644
index 0000000..01445c6
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/PageView.qml
@@ -0,0 +1,705 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+import QtWebEngine 1.1
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Layouts 1.2
+import QtGraphicalEffects 1.0
+
+import WebBrowser 1.0
+import "assets"
+
+Rectangle {
+ id: root
+
+ property int itemWidth: browserWindow.width / 2
+ property int itemHeight: browserWindow.height / 2
+
+ property bool interactive: true
+
+ property alias currentIndex: pathView.currentIndex
+ property alias count: pathView.count
+
+ property string viewState: "page"
+
+ onViewStateChanged: {
+ if (viewState == "page" || viewState == "fullscreen")
+ homeScreen.state = "disabled"
+ }
+
+ property QtObject otrProfile: WebEngineProfile {
+ offTheRecord: true
+ }
+
+ property QtObject defaultProfile: WebEngineProfile {
+ storageName: "YABProfile"
+ offTheRecord: false
+ }
+
+ Component {
+ id: tabComponent
+ Rectangle {
+ id: tabItem
+ property alias webView: webEngineView
+ property alias title: webEngineView.title
+
+ property var image: QtObject {
+ property var snapshot: null
+ property string url: "about:blank"
+ }
+
+ visible: opacity != 0.0
+
+ Behavior on opacity {
+ NumberAnimation { duration: animationDuration }
+ }
+
+ anchors.fill: parent
+
+ Action {
+ shortcut: "Ctrl+F"
+ onTriggered: {
+ findBar.visible = !findBar.visible
+ if (findBar.visible) {
+ findTextField.forceActiveFocus()
+ }
+ }
+ }
+
+ FeaturePermissionBar {
+ id: permBar
+ view: webEngineView
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ }
+ z: 3
+ }
+
+ WebEngineView {
+ id: webEngineView
+
+ anchors {
+ fill: parent
+ top: permBar.bottom
+ }
+
+ profile: settingsView.privateBrowsingEnabled ? otrProfile : defaultProfile
+ enabled: root.interactive
+
+ function takeSnapshot() {
+ if (webEngineView.url == "" || webEngineView.url == "about:blank") {
+ tabItem.image.url = "about:blank"
+ tabItem.image.snapshot = null
+ return
+ }
+
+ if (tabItem.image.url == webEngineView.url || tabItem.opacity != 1.0)
+ return
+
+ tabItem.image.url = webEngineView.url
+ webEngineView.grabToImage(function(result) {
+ tabItem.image.snapshot = result;
+ console.log("takeSnapshot("+result.url+")")
+ });
+ }
+
+ // Trigger a refresh to check if the new url is bookmarked.
+ onUrlChanged: navigation.refresh()
+
+
+ settings.autoLoadImages: settingsView.autoLoadImages
+ settings.javascriptEnabled: !settingsView.javaScriptDisabled
+
+ // This should be enabled as we can switch to Qt 5.6 (i.e. import QtWebEngine 1.2)
+ // settings.pluginsEnabled: settingsView.pluginsEnabled
+
+ onLoadingChanged: {
+ if (loading)
+ navigation.state = "enabled"
+ }
+
+ onCertificateError: {
+ if (!acceptedCertificates.shouldAutoAccept(error)){
+ error.defer()
+ sslDialog.enqueue(error)
+ } else{
+ error.ignoreCertificateError()
+ }
+ }
+
+ onNewViewRequested: {
+ webEngineView.takeSnapshot()
+ var tab
+ if (!request.userInitiated) {
+ print("Warning: Blocked a popup window.")
+ return
+ }
+
+ tab = tabView.createEmptyTab()
+
+ if (!tab)
+ return
+
+ if (request.destination == WebEngineView.NewViewInTab) {
+ pathView.positionViewAtIndex(tabView.count - 1, PathView.Center)
+ request.openIn(tab.webView)
+ } else if (request.destination == WebEngineView.NewViewInBackgroundTab) {
+ var index = pathView.currentIndex
+ request.openIn(tab.webView)
+ pathView.positionViewAtIndex(index, PathView.Center)
+ } else if (request.destination == WebEngineView.NewViewInDialog) {
+ request.openIn(tab.webView)
+ } else {
+ request.openIn(tab.webView)
+ }
+ }
+
+ onFeaturePermissionRequested: {
+ permBar.securityOrigin = securityOrigin;
+ permBar.requestedFeature = feature;
+ permBar.visible = true;
+ }
+
+ onFullScreenRequested: {
+ if (request.toggleOn)
+ viewState = "fullscreen"
+ else
+ viewState = "page"
+ request.accept()
+ }
+ }
+
+ Desaturate {
+ id: desaturate
+ visible: desaturation != 0.0
+ anchors.fill: webEngineView
+ source: webEngineView
+ desaturation: root.interactive ? 0.0 : 1.0
+
+ Behavior on desaturation {
+ NumberAnimation { duration: animationDuration }
+ }
+ }
+
+ FastBlur {
+ id: blur
+ visible: radius != 0.0
+ anchors.fill: desaturate
+ source: desaturate
+ radius: desaturate.desaturation * 25
+ }
+
+ TouchTracker {
+ id: tracker
+ enabled: root.interactive
+ target: webEngineView
+ anchors.fill: parent
+ onTouchYChanged: browserWindow.touchY = tracker.touchY
+ onYVelocityChanged: browserWindow.velocityY = yVelocity
+ onTouchBegin: {
+ browserWindow.touchY = tracker.touchY
+ browserWindow.velocityY = yVelocity
+ browserWindow.touchReference = tracker.touchY
+ browserWindow.touchGesture = true
+ navigation.state = "tracking"
+ }
+ onTouchEnd: {
+ browserWindow.velocityY = yVelocity
+ browserWindow.touchGesture = false
+ navigation.state = "tracking"
+ }
+ onScrollDirectionChanged: {
+ browserWindow.velocityY = 0
+ browserWindow.touchReference = tracker.touchY
+ }
+ }
+
+ Rectangle {
+ opacity: {
+ if (inputPanel.state === "visible")
+ return 0.0
+ if (webEngineView.url == "" || webEngineView.url == "about:blank")
+ return 1.0
+ return 0.0
+ }
+ anchors.fill: parent
+ visible: opacity != 0.0
+ color: "#09102b"
+ Image {
+ id: placeholder
+ y: placeholder.height - navigation.y
+ anchors.horizontalCenter: parent.horizontalCenter
+ source: "assets/icons/AppLogoColor.png"
+ }
+ Text {
+ id: label
+ anchors {
+ top: placeholder.bottom
+ topMargin: 20
+ horizontalCenter: placeholder.horizontalCenter
+ }
+ font.family: defaultFontFamily
+ font.pixelSize: 28
+ color: "white"
+ text: "Qt WebBrowser"
+ }
+
+ Behavior on opacity {
+ NumberAnimation { duration: animationDuration }
+ }
+ }
+
+ Rectangle {
+ id: findBar
+ anchors {
+ right: webEngineView.right
+ left: webEngineView.left
+ top: webEngineView.top
+ }
+ height: toolBarSize / 2 + 10
+ visible: false
+ color: uiColor
+
+ RowLayout {
+ spacing: 0
+ anchors.fill: parent
+ Rectangle {
+ width: 5
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiColor
+ }
+ TextField {
+ id: findTextField
+ Layout.fillWidth: true
+ onAccepted: {
+ webEngineView.findText(text)
+ }
+ style: TextFieldStyle {
+ textColor: "black"
+ font.family: defaultFontFamily
+ font.pixelSize: 28
+ selectionColor: uiHighlightColor
+ selectedTextColor: "black"
+ placeholderTextColor: placeholderColor
+ background: Rectangle {
+ implicitWidth: 514
+ implicitHeight: toolBarSize / 2
+ border.color: textFieldStrokeColor
+ border.width: 1
+ }
+ }
+ }
+ Rectangle {
+ width: 5
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiColor
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ UIButton {
+ id: findBackwardButton
+ iconSource: "assets/icons/Btn_Back.png"
+ implicitHeight: parent.height
+ onClicked: webEngineView.findText(findTextField.text, WebEngineView.FindBackward)
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ UIButton {
+ id: findForwardButton
+ iconSource: "assets/icons/Btn_Forward.png"
+ implicitHeight: parent.height
+ onClicked: webEngineView.findText(findTextField.text)
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: uiSeparatorColor
+ }
+ UIButton {
+ id: findCancelButton
+ iconSource: "assets/icons/Btn_Clear.png"
+ implicitHeight: parent.height
+ onClicked: findBar.visible = false
+ }
+ }
+ }
+ }
+ }
+
+ ListModel {
+ id: listModel
+ }
+
+ function makeCurrent(index) {
+ viewState = "list"
+ pathView.positionViewAtIndex(index, PathView.Center)
+ viewState = "page"
+ }
+
+ function createEmptyTab() {
+ var tab = add(tabComponent)
+ return tab
+ }
+
+ function add(component) {
+ if (listModel.count === tabViewMaxTabs) {
+ homeScreen.messageBox.state = "tabsfull"
+ homeScreen.state = "enabled"
+ homeScreen.forceActiveFocus()
+ return null
+ }
+
+ var element = {"item": null }
+ element.item = component.createObject(root, { "width": root.width, "height": root.height, "opacity": 0.0 })
+
+ if (element.item == null) {
+ console.log("PageView::add(): Error creating object");
+ return
+ }
+
+ listModel.append(element)
+ return element.item
+ }
+
+ function remove(index) {
+ pathView.interactive = false
+ pathView.currentItem.state = ""
+ pathView.currentItem.visible = false
+ listModel.remove(index)
+ pathView.decrementCurrentIndex()
+ pathView.interactive = true
+ }
+
+ function get(index) {
+ return listModel.get(index)
+ }
+
+ Component {
+ id: delegate
+
+ Rectangle {
+ id: wrapper
+
+ parent: item
+
+ property real visibility: 0.0
+ property bool isCurrentItem: PathView.isCurrentItem
+
+ visible: PathView.onPath && visibility != 0.0
+ state: isCurrentItem ? root.viewState : "list"
+
+ Behavior on scale {
+ NumberAnimation { duration: animationDuration }
+ }
+
+ states: [
+ State {
+ name: "page"
+ PropertyChanges { target: wrapper; width: root.width; height: root.height; visibility: 0.0 }
+ PropertyChanges { target: pathView; interactive: false }
+ PropertyChanges { target: item; opacity: 1.0 }
+ PropertyChanges { target: navigation; state: "enabled" }
+ },
+ State {
+ name: "list"
+ PropertyChanges { target: wrapper; width: itemWidth; height: itemHeight; visibility: 1.0 }
+ PropertyChanges { target: pathView; interactive: true }
+ PropertyChanges { target: item; opacity: 0.0 }
+ },
+ State {
+ name: "fullscreen"
+ PropertyChanges { target: wrapper; width: root.width; height: root.height; visibility: 0.0 }
+ PropertyChanges { target: pathView; interactive: false }
+ PropertyChanges { target: item; opacity: 1.0 }
+ PropertyChanges { target: navigation; state: "disabled" }
+ }
+ ]
+
+ transitions: Transition {
+ ParallelAnimation {
+ PropertyAnimation { property: "visibility"; duration: animationDuration; easing.type : Easing.InSine }
+ PropertyAnimation { properties: "x,y"; duration: animationDuration; easing.type: Easing.InSine }
+ PropertyAnimation { properties: "width,height"; duration: animationDuration; easing.type: Easing.InSine }
+ }
+ }
+
+ width: itemWidth; height: itemHeight
+ scale: {
+ if (pathView.count == 1)
+ return 1.0
+ if (pathView.count < 4)
+ return isCurrentItem ? 1.0 : 0.5
+
+ if (isCurrentItem)
+ return 1.0
+
+ var index1 = pathView.currentIndex - 2
+ var index2 = pathView.currentIndex - 1
+ var index4 = (pathView.currentIndex + 1) % pathView.count
+ var index5 = (pathView.currentIndex + 2) % pathView.count
+
+ if (index1 < 0)
+ index1 = pathView.count + index1
+ if (index2 < 0)
+ index2 = pathView.count + index2
+
+ switch (index) {
+ case index1 :
+ return 0.25
+ case index2:
+ return 0.5
+ case index4:
+ return 0.5
+ case index5:
+ return 0.25
+ }
+
+ return 0.25
+ }
+ z: PathView.itemZ
+
+ MouseArea {
+ enabled: pathView.interactive
+ anchors.fill: wrapper
+ onClicked: {
+ mouse.accepted = true
+ if (index < 0)
+ return
+
+ if (index == pathView.currentIndex) {
+ if (root.viewState == "list")
+ root.viewState = "page"
+ return
+ }
+ pathView.currentIndex = index
+ }
+ }
+ Rectangle {
+ id: shadow
+ visible: false
+ property real size: 24
+ anchors {
+ top: parent.top
+ topMargin: 9
+ horizontalCenter: parent.horizontalCenter
+ }
+ color: iconOverlayColor
+ radius: size / 2
+ width: snapshot.width
+ height: snapshot.height
+ }
+ GaussianBlur {
+ anchors.fill: shadow
+ source: shadow
+ radius: shadow.size
+ samples: shadow.size * 2
+ opacity: 0.3
+ transparentBorder: true
+ visible: wrapper.visibility == 1.0
+ }
+
+ Rectangle {
+ id: snapshot
+ color: uiColor
+
+ Image {
+ source: {
+ if (!item.image.snapshot)
+ return "assets/icons/about_blank.png"
+ return item.image.snapshot.url
+ }
+ anchors.fill: parent
+ Rectangle {
+ enabled: index == pathView.currentIndex && !pathView.moving && !pathView.flicking && wrapper.visibility == 1.0
+ opacity: enabled ? 1.0 : 0.0
+ visible: wrapper.visibility == 1.0 && listModel.count > 1
+ width: image.sourceSize.width
+ height: image.sourceSize.height - 2
+ radius: width / 2
+ color: iconOverlayColor
+ anchors {
+ horizontalCenter: parent.right
+ verticalCenter: parent.top
+ }
+ Image {
+ id: image
+ opacity: {
+ if (closeButton.pressed)
+ return 0.70
+ return 1.0
+ }
+ anchors {
+ top: parent.top
+ left: parent.left
+ }
+ source: "assets/icons/Btn_Delete.png"
+ MouseArea {
+ id: closeButton
+ anchors.fill: parent
+ onClicked: {
+ mouse.accepted = true
+ remove(pathView.currentIndex)
+ }
+ }
+ }
+ Behavior on opacity {
+ NumberAnimation { duration: animationDuration / 2 }
+ }
+ }
+ }
+ anchors.fill: wrapper
+ }
+
+ Text {
+ anchors {
+ topMargin: -25
+ top: parent.top
+ horizontalCenter: parent.horizontalCenter
+ }
+ horizontalAlignment: Text.AlignHCenter
+ width: parent.width - image.width
+ elide: Text.ElideRight
+ text: item.title
+ font.pixelSize: 16
+ font.family: defaultFontFamily
+ color: "white"
+ visible: wrapper.isCurrentItem && wrapper.visibility == 1.0
+ }
+ }
+ }
+
+ Rectangle {
+ color: "#09102b"
+ anchors.fill: parent
+ }
+
+ PathView {
+ id: pathView
+ pathItemCount: 5
+ anchors.fill: parent
+ model: listModel
+ delegate: delegate
+ highlightMoveDuration: animationDuration
+ highlightRangeMode: PathView.StrictlyEnforceRange
+ snapMode: PathView.SnapToItem
+ preferredHighlightBegin: 0.5
+ preferredHighlightEnd: 0.5
+
+ dragMargin: itemHeight
+
+ focus: pathView.interactive
+
+ property real offset: 30
+
+ property real margin: {
+ if (count == 2)
+ return root.width / 4 - offset
+ if (count == 3)
+ return root.width / 8 + offset
+ if (count == 4)
+ return root.width / 8 - offset
+
+ return offset
+ }
+
+ property real middle: {
+ if (currentItem)
+ return (pathView.height / 2) - (currentItem.visibility * 50)
+ return (pathView.height / 2 - 50)
+ }
+
+ path: Path {
+ startX: pathView.margin
+ startY: pathView.middle
+
+ PathPercent { value: 0.0 }
+ PathAttribute { name: "itemZ"; value: 0 }
+ PathLine {
+ x: (pathView.width - itemWidth) / 2 + 106
+ y: pathView.middle
+ }
+ PathPercent { value: 0.49 }
+ PathAttribute { name: "itemZ"; value: 6 }
+
+ PathLine { relativeX: 0; relativeY: 0 }
+
+ PathLine {
+ x: (pathView.width - itemWidth) / 2 + itemWidth - 106
+ y: pathView.middle
+ }
+ PathPercent { value: 0.51 }
+
+ PathLine { relativeX: 0; relativeY: 0 }
+
+ PathAttribute { name: "itemZ"; value: 4 }
+ PathLine {
+ x: pathView.width - pathView.margin
+ y: pathView.middle
+ }
+ PathPercent { value: 1 }
+ PathAttribute { name: "itemZ"; value: 2 }
+ }
+
+ Keys.onLeftPressed: decrementCurrentIndex()
+ Keys.onRightPressed: incrementCurrentIndex()
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/SettingsView.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/SettingsView.qml
new file mode 100644
index 0000000..81397fd
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/SettingsView.qml
@@ -0,0 +1,195 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+import QtQuick.Layouts 1.0
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import Qt.labs.settings 1.0
+
+import WebBrowser 1.0
+
+Rectangle {
+ id: root
+ color: "#09102b"
+ property bool privateBrowsingEnabled: appSettings[0].active
+ property bool httpDiskCacheEnabled: appSettings[1].active
+ property bool autoLoadImages: appSettings[2].active
+ property bool javaScriptDisabled: appSettings[3].active
+ // property bool pluginsEnabled: appSettings[4].active
+
+ property var appSettings: [
+ { "name": "Private Browsing", "active": false, "notify": function(v) { privateBrowsingEnabled = v; } },
+ { "name": "Enable HTTP Disk Cache", "active": true, "notify": function(v) { httpDiskCacheEnabled = v; } },
+ { "name": "Auto Load Images", "active": true, "notify": function(v) { autoLoadImages = v; } },
+ { "name": "Disable JavaScript", "active": false, "notify": function(v) { javaScriptDisabled = v; } },
+// { "name": "Enable Plugins", "active": false, "notify": function(v) { pluginsEnabled = v; } }
+ ]
+
+ function save() {
+ for (var i = 0; i < appSettings.length; ++i) {
+ var setting = appSettings[i]
+
+ listModel.get(i).active = setting.active
+ // Do not persist private browsing mode
+ if (setting.name === "Private Browsing")
+ continue
+ AppEngine.saveSetting(setting.name, setting.active)
+ }
+ }
+
+ Rectangle{
+ color: toolBarSeparatorColor
+ height: 2
+ anchors.top: root.top
+ anchors.right: root.right
+ anchors.left: root.left
+ z: 100
+ }
+
+ state: "disabled"
+
+ states: [
+ State {
+ name: "enabled"
+ AnchorChanges {
+ target: root
+ anchors.top: navigation.bottom
+ }
+ PropertyChanges {
+ target: settingsToolBar
+ opacity: 1.0
+ }
+ },
+ State {
+ name: "disabled"
+ AnchorChanges {
+ target: root
+ anchors.top: root.parent.bottom
+ }
+ PropertyChanges {
+ target: settingsToolBar
+ opacity: 0.0
+ }
+ }
+ ]
+
+ transitions: Transition {
+ AnchorAnimation { duration: animationDuration; easing.type : Easing.InSine }
+ }
+
+ ListModel {
+ id: listModel
+ }
+
+ ListView {
+ id: listView
+ leftMargin: 230
+ rightMargin: leftMargin
+ anchors.fill: parent
+ model: listModel
+ delegate: Rectangle {
+ color: "transparent"
+ height: 100
+ width: 560
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ font.family: defaultFontFamily
+ font.pixelSize: 28
+ text: name
+ color: sw.enabled ? "white" : "#848895"
+ }
+ Rectangle {
+ anchors {
+ right: parent.right
+ verticalCenter: parent.verticalCenter
+ }
+ CustomSwitch {
+ id: sw
+ onCheckedChanged: {
+ var setting = appSettings[index]
+ setting.active = checked
+ setting.notify(checked)
+ }
+ enabled: {
+ var ok = appSettings[index].name.indexOf("Disk Cache") < 0
+ return ok || !privateBrowsingEnabled
+ }
+ anchors.centerIn: parent
+ checked: {
+ if (enabled)
+ return active
+ return false
+ }
+ /*style: SwitchStyle {
+ handle: Rectangle {
+ width: 42
+ height: 42
+ radius: height / 2
+ color: "white"
+ border.color: control.checked ? "#5caa14" : "#9b9b9b"
+ border.width: 1
+ }
+
+ groove: Rectangle {
+ implicitWidth: 72
+ height: 42
+ radius: height / 2
+ border.color: control.checked ? "#5caa14" : "#9b9b9b"
+ color: control.checked ? "#5cff14" : "white"
+ border.width: 1
+ }
+ }*/
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ for (var i = 0; i < appSettings.length; ++i) {
+ var setting = appSettings[i]
+ var active = JSON.parse(AppEngine.restoreSetting(setting.name, setting.active))
+ if (setting.active !== active) {
+ setting.active = active
+ setting.notify(active)
+ }
+ listModel.append(setting)
+ }
+ listView.forceLayout()
+ }
+ Component.onDestruction: root.save()
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Utils.js b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Utils.js
new file mode 100644
index 0000000..88e2bbe
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/Utils.js
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+. pragma library
+
+function quote(str, delimiter) {
+ // discuss at: http://phpjs.org/functions/preg_quote/
+ // original by: booeyOH
+ // improved by: Ates Goral (http://magnetiq.com)
+ // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // improved by: Brett Zamir (http://brett-zamir.me)
+ // bugfixed by: Onno Marsman
+ // example 1: preg_quote("$40");
+ // returns 1: '\\$40'
+ // example 2: preg_quote("*RRRING* Hello?");
+ // returns 2: '\\*RRRING\\* Hello\\?'
+ // example 3: preg_quote("\\.+*?[^]$(){}=!<>|:");
+ // returns 3: '\\\\\\.\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:'
+
+ return String(str)
+ .replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
+}
+
+function highlight( text, search )
+{
+ return text.replace( new RegExp( "(" + quote( search ) + ")" , 'gi' ), "<b>$1</b>" );
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/UIButton.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/UIButton.qml
new file mode 100644
index 0000000..cc9351f
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/UIButton.qml
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Layouts 1.2
+
+
+ToolButton {
+ id: root
+ implicitHeight: toolBarSize
+ implicitWidth: toolBarSize
+
+ property alias buttonText: label.text
+ property alias textColor: label.color
+ property alias textSize: label.font.pixelSize
+ property string source: ""
+ property real radius: 0.0
+ property string color: uiColor
+ property string highlightColor: buttonPressedColor
+ Text {
+ id: label
+ color: "white"
+ anchors.centerIn: parent
+ font.family: defaultFontFamily
+ font.pixelSize: 28
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ opacity: root.enabled ? 1.0 : 0.3
+ color: root.pressed || root.checked ? root.highlightColor : root.color
+ radius: root.radius
+ Image {
+ source: root.source
+ width: Math.min(sourceSize.width, root.width)
+ height: Math.min(sourceSize.height, root.height)
+ anchors.centerIn: parent
+ }
+ }
+ }
+}
+
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/UIToolBar.qml b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/UIToolBar.qml
new file mode 100644
index 0000000..39a0974
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/UIToolBar.qml
@@ -0,0 +1,168 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+import QtQuick.Controls 1.0
+import QtQuick.Controls.Styles 1.0
+import QtQuick.Layouts 1.0
+
+ToolBar {
+ id: root
+
+ property alias title: titleBox.text
+ property alias source: toolBarButton.source
+ property alias indicator: indicatorText.text
+
+ property int indicatorWidth: 40
+ property int indicatorHeight: 32
+
+ signal optionClicked()
+ signal doneClicked()
+
+ height: navigation.height
+
+ style: ToolBarStyle {
+ background: Rectangle {
+ color: toolBarFillColor
+ }
+ padding {
+ left: 0
+ right: 0
+ top: 0
+ bottom: 0
+ }
+ }
+
+ RowLayout {
+ spacing: 0
+ height: toolBarSize
+ anchors.fill: parent
+ Rectangle {
+ width: childrenRect.width
+ anchors {
+ left: parent.left
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: toolBarFillColor
+ Text {
+ id: titleBox
+ visible: root.title !== ""
+ anchors {
+ leftMargin: visible ? 30 : 0
+ left: parent.left
+ verticalCenter: parent.verticalCenter
+ }
+ color: "white"
+ font.pixelSize: 28
+ font.family: defaultFontFamily
+ }
+ Rectangle {
+ visible: toolBarButton.visible && titleBox.visible
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: toolBarSeparatorColor
+ }
+ UIButton {
+ id: toolBarButton
+ visible: root.source !== ""
+ color: toolBarFillColor
+ Component.onCompleted: toolBarButton.clicked.connect(root.optionClicked)
+ anchors.left: titleBox.right
+ }
+ }
+ Rectangle {
+ visible: toolBarButton.visible
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: toolBarSeparatorColor
+ }
+ Rectangle {
+ width: indicatorWidth
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: toolBarFillColor
+ }
+ Rectangle {
+ color: toolBarFillColor
+ Layout.fillWidth: true
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ Rectangle {
+ visible: root.indicator !== ""
+ color: "transparent"
+ border.color: "white"
+ border.width: 2
+ width: indicatorWidth
+ height: indicatorHeight
+ anchors.centerIn: parent
+ Text {
+ id: indicatorText
+ anchors.centerIn: parent
+ color: "white"
+ font.family: defaultFontFamily
+ font.pixelSize: 20
+ }
+ }
+ }
+ Rectangle {
+ width: 1
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ color: toolBarSeparatorColor
+ }
+ UIButton {
+ id: doneButton
+ color: toolBarFillColor
+ buttonText: "Done"
+ implicitWidth: 120
+ Component.onCompleted: doneButton.clicked.connect(root.doneClicked)
+ }
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/AppLogoColor.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/AppLogoColor.png
new file mode 100644
index 0000000..2a49717
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/AppLogoColor.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/AppLogoGrey.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/AppLogoGrey.png
new file mode 100644
index 0000000..b2baae5
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/AppLogoGrey.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Add.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Add.png
new file mode 100644
index 0000000..3c45c42
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Add.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Back.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Back.png
new file mode 100644
index 0000000..562c9f6
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Back.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmark_Checked.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmark_Checked.png
new file mode 100644
index 0000000..a6dbe6a
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmark_Checked.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmark_Indicator.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmark_Indicator.png
new file mode 100644
index 0000000..a8b8b6b
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmark_Indicator.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmarks.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmarks.png
new file mode 100644
index 0000000..fc286cc
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Bookmarks.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Clear.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Clear.png
new file mode 100644
index 0000000..1c9870a
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Clear.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Delete.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Delete.png
new file mode 100644
index 0000000..2010838
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Delete.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Forward.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Forward.png
new file mode 100644
index 0000000..e4c96f8
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Forward.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Home.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Home.png
new file mode 100644
index 0000000..7358a59
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Home.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Reload.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Reload.png
new file mode 100644
index 0000000..cff41cd
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Reload.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Search.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Search.png
new file mode 100644
index 0000000..a6ef383
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Search.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Settings.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Settings.png
new file mode 100644
index 0000000..33d7400
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Settings.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Tabs.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Tabs.png
new file mode 100644
index 0000000..c007408
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Tabs.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Up.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Up.png
new file mode 100644
index 0000000..f70a78d
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Btn_Up.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Error_Icon.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Error_Icon.png
new file mode 100644
index 0000000..cf40696
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/Error_Icon.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/LightWebBrowser_Icons.svg b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/LightWebBrowser_Icons.svg
new file mode 100644
index 0000000..fce4b40
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/LightWebBrowser_Icons.svg
@@ -0,0 +1,243 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="1024px" height="768px" viewBox="0 0 1024 768" style="enable-background:new 0 0 1024 768;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:none;}
+ .st1{fill:#FFFFFF;}
+ .st2{fill:#BDBEBF;}
+ .st3{opacity:0.25;}
+ .st4{fill:#3882AE;}
+ .st5{fill:#46A2DA;}
+ .st6{opacity:0.8;}
+ .st7{fill:#FDDD5C;}
+ .st8{fill:#80C342;}
+ .st9{fill:#D6D6D6;}
+</style>
+<g>
+ <rect class="st0" width="1024" height="768"/>
+ <g id="Layer_1_1_">
+ <rect x="512" width="512" height="768"/>
+ </g>
+ <g id="Layer_2">
+ <g>
+ <g>
+ <path class="st1" d="M569.6,71.1c-0.3,0-0.5-0.1-0.7-0.3L554,56l14.8-14.8c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4L556.8,56l13.4,13.4
+ c0.4,0.4,0.4,1,0,1.4C570.1,71,569.8,71.1,569.6,71.1z"/>
+ </g>
+ <g>
+ <rect x="524" y="16" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M640.4,71.1c0.3,0,0.5-0.1,0.7-0.3L656,56l-14.8-14.8c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4L653.2,56
+ l-13.4,13.4c-0.4,0.4-0.4,1,0,1.4C639.9,71,640.2,71.1,640.4,71.1z"/>
+ </g>
+ <g>
+ <rect x="606" y="16" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ <g>
+ <path class="st1" d="M728,43.5l13,9.5v16h-26V53L728,43.5 M728,41l-15,11v19h30V52L728,41L728,41z"/>
+ <g>
+ <g>
+ <rect x="688" y="16" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <rect x="770" y="16" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ <g>
+ <path class="st1" d="M824,70h-28V48h28V70z M798,68h24V50h-24V68z"/>
+ <polygon class="st1" points="812,42 812,48 814,48 814,44 828,44 828,58 824,58 824,60 830,60 830,42 "/>
+ <polygon class="st1" points="796,58 792,58 792,44 806,44 806,48 808,48 808,42 790,42 790,60 796,60 "/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M901.9,72c-0.2,0-0.4,0-0.5-0.1l-9.4-5.8l-9.4,5.8c-0.3,0.2-0.8,0.2-1.1,0c-0.3-0.2-0.5-0.7-0.4-1.1
+ l2.7-10.5l-8.5-7c-0.3-0.3-0.4-0.7-0.3-1.1s0.5-0.7,0.9-0.7l11-0.8l4.1-10.1c0.2-0.4,0.5-0.6,0.9-0.6s0.8,0.2,0.9,0.6l4.1,10.1
+ l11,0.8c0.4,0,0.8,0.3,0.9,0.7c0.1,0.4,0,0.8-0.3,1.1l-8.5,7l2.7,10.5c0.1,0.4,0,0.8-0.4,1.1C902.3,71.9,902.1,72,901.9,72z
+ M892,63.9c0.2,0,0.4,0,0.5,0.1l7.8,4.8l-2.2-8.8c-0.1-0.4,0-0.8,0.3-1l7-5.8l-9.1-0.6c-0.4,0-0.7-0.3-0.9-0.6l-3.4-8.4
+ l-3.4,8.4c-0.1,0.4-0.5,0.6-0.9,0.6l-9.1,0.6l7,5.8c0.3,0.2,0.4,0.6,0.3,1l-2.2,8.8l7.8-4.8C891.6,63.9,891.8,63.9,892,63.9z"/>
+ </g>
+ <g>
+ <rect x="852" y="16" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <circle class="st1" cx="974" cy="44" r="3"/>
+ <circle class="st1" cx="974" cy="56" r="3"/>
+ <circle class="st1" cx="974" cy="68" r="3"/>
+ </g>
+ <g>
+ <rect x="934" y="16" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <rect x="42" y="16" class="st0" width="80" height="80"/>
+ </g>
+ <g>
+ <path class="st2" d="M78.5,43.5c5,0,9,4,9,9s-4,9-9,9s-9-4-9-9S73.5,43.5,78.5,43.5 M78.5,41C72.1,41,67,46.1,67,52.5
+ C67,58.9,72.1,64,78.5,64S90,58.9,90,52.5C90,46.1,84.9,41,78.5,41L78.5,41z"/>
+ <path class="st2" d="M96.8,67.9c0.3,0.3,0.3,0.8,0,1.2L95,70.8c-0.3,0.3-0.8,0.3-1.2,0l-9.1-9.1c-0.3-0.3-0.3-0.8,0-1.2l1.7-1.7
+ c0.3-0.3,0.8-0.3,1.2,0L96.8,67.9z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st2" d="M259,41c-8.3,0-15,6.7-15,15s6.7,15,15,15s15-6.7,15-15S267.3,41,259,41z M266.8,62.4l-1.4,1.4l-6.4-6.4
+ l-6.4,6.4l-1.4-1.4l6.4-6.4l-6.4-6.4l1.4-1.4l6.4,6.4l6.4-6.4l1.4,1.4l-6.4,6.4L266.8,62.4z"/>
+ </g>
+ </g>
+ <rect x="232" y="29" class="st0" width="54" height="54"/>
+ </g>
+ <g>
+ <rect x="326" y="29" class="st0" width="54" height="54"/>
+ <g>
+ <path class="st2" d="M363.9,61.1C362,65.2,357.8,68,353,68c-6.6,0-12-5.4-12-12c0-6.6,5.4-12,12-12c4,0,7.5,2,9.7,5h2.4
+ c-2.4-4.2-6.9-7-12.1-7c-7.7,0-14,6.3-14,14s6.3,14,14,14c5.6,0,10.4-3.3,12.7-8.1L363.9,61.1z"/>
+ <polygon class="st2" points="367.4,52 357.5,52 367.4,42.2 "/>
+ </g>
+ </g>
+ <g>
+ <rect x="420" y="29" class="st0" width="54" height="54"/>
+ <g>
+ <g class="st3">
+ <circle cx="447" cy="57" r="25"/>
+ </g>
+ <g>
+ <path class="st1" d="M447,79.5c-13.5,0-24.5-11-24.5-24.5s11-24.5,24.5-24.5s24.5,11,24.5,24.5S460.5,79.5,447,79.5z"/>
+ <path class="st4" d="M447,31c13.2,0,24,10.8,24,24s-10.8,24-24,24s-24-10.8-24-24S433.8,31,447,31 M447,30
+ c-13.8,0-25,11.2-25,25s11.2,25,25,25s25-11.2,25-25S460.8,30,447,30L447,30z"/>
+ </g>
+ <g>
+
+ <rect x="433" y="54" transform="matrix(0.7071 0.7071 -0.7071 0.7071 169.8026 -299.9675)" class="st4" width="28" height="2"/>
+
+ <rect x="433" y="54" transform="matrix(-0.7071 0.7071 -0.7071 -0.7071 801.9339 -222.1632)" class="st4" width="28" height="2"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <rect x="563" y="162" class="st1" width="2" height="28"/>
+ <rect x="550" y="175" class="st1" width="28" height="2"/>
+ </g>
+ <g>
+ <rect x="524" y="136" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st1" d="M73,193c-2.8,0-5-2.2-5-5v-46c0-2.8,2.2-5,5-5h66c2.8,0,5,2.2,5,5v46c0,2.8-2.2,5-5,5H73z"/>
+ <path class="st5" d="M139,138c2.2,0,4,1.8,4,4v46c0,2.2-1.8,4-4,4H73c-2.2,0-4-1.8-4-4v-46c0-2.2,1.8-4,4-4H139 M139,136H73
+ c-3.3,0-6,2.7-6,6v46c0,3.3,2.7,6,6,6h66c3.3,0,6-2.7,6-6v-46C145,138.7,142.3,136,139,136L139,136z"/>
+ </g>
+ <polygon class="st1" points="109,187 93,187 93,194 108,211 108,194 "/>
+ <rect x="107" y="192" class="st5" width="2" height="20"/>
+ <polygon class="st5" points="107,209 107,212 91,192 93,192 "/>
+ </g>
+ <g>
+ <g>
+ <path class="st5" d="M109.6,165.2c0-3.5,0.5-6.7,1.5-9.7s2.5-5.6,4.4-7.9h2.2c-1.9,2.4-3.4,5.1-4.4,8.1s-1.5,6.2-1.5,9.5
+ c0,6.3,2,12.1,5.9,17.3h-2.2c-1.9-2.2-3.4-4.8-4.4-7.8S109.6,168.6,109.6,165.2z"/>
+ </g>
+ <g>
+ <g>
+ <path class="st5" d="M123.5,156c-1.1-1.8-3.2-2.8-5.7-2.8c-1.3,0-2.7,0.3-3.9,0.8c-0.2,0.6-0.5,1.1-0.7,1.7
+ c-0.1,0.4-0.2,0.7-0.3,1.1c0,0,0.1,0,0.1-0.1c1.5-1,3.2-1.5,4.8-1.5c1.8,0,3.3,0.7,4.1,1.9c1.5,2.4,0,6.1-3.4,8.2
+ c-1.5,1-3.2,1.5-4.8,1.5c-0.7,0-1.4-0.1-1.9-0.3c0,0.7,0.1,1.4,0.2,2c0.5,0.1,1.1,0.2,1.7,0.2c2,0,4.1-0.6,5.9-1.8
+ C123.8,164.2,125.6,159.3,123.5,156z"/>
+ </g>
+ <path class="st5" d="M118.6,159.3c-0.2-0.5-0.8-0.7-1.3-0.4l-5.4,2.7c-0.1,0.7-0.1,1.5-0.2,2.2l6.4-3.3
+ C118.7,160.4,118.9,159.8,118.6,159.3z"/>
+ </g>
+ <g>
+ <rect x="88" y="176" transform="matrix(0.7071 0.7071 -0.7071 0.7071 152.9725 -15.3456)" class="st5" width="14" height="2"/>
+
+ <rect x="88" y="176" transform="matrix(-0.7071 0.7071 -0.7071 -0.7071 287.3204 234.9828)" class="st5" width="14" height="2"/>
+ </g>
+ <g>
+ <rect x="88" y="152" transform="matrix(0.7071 0.7071 -0.7071 0.7071 136.003 -22.3749)" class="st5" width="14" height="2"/>
+
+ <rect x="88" y="152" transform="matrix(-0.7071 0.7071 -0.7071 -0.7071 270.3508 194.0141)" class="st5" width="14" height="2"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g class="st6">
+ <rect x="185" y="136" class="st5" width="70" height="70"/>
+ </g>
+ <g>
+ <path class="st1" d="M204.9,177.6c0-0.3,0.1-0.5,0.3-0.7L220,162l14.8,14.8c0.4,0.4,0.4,1,0,1.4s-1,0.4-1.4,0L220,164.8
+ l-13.4,13.4c-0.4,0.4-1,0.4-1.4,0C205,178.1,204.9,177.8,204.9,177.6z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st7" d="M655.9,192c-0.2,0-0.4,0-0.5-0.1l-9.4-5.8l-9.4,5.8c-0.3,0.2-0.8,0.2-1.1,0c-0.3-0.2-0.5-0.7-0.4-1.1
+ l2.7-10.5l-8.5-7c-0.3-0.3-0.4-0.7-0.3-1.1c0.1-0.4,0.5-0.7,0.9-0.7l11-0.8l4.1-10.1c0.2-0.4,0.5-0.6,0.9-0.6s0.8,0.2,0.9,0.6
+ l4.1,10.1l11,0.8c0.4,0,0.8,0.3,0.9,0.7s0,0.8-0.3,1.1l-8.5,7l2.7,10.5c0.1,0.4,0,0.8-0.4,1.1C656.3,191.9,656.1,192,655.9,192z
+ "/>
+ </g>
+ <g>
+ <rect x="606" y="136" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st2" d="M173.9,72c-0.2,0-0.4,0-0.5-0.1l-9.4-5.8l-9.4,5.8c-0.3,0.2-0.8,0.2-1.1,0s-0.5-0.7-0.4-1.1l2.7-10.5
+ l-8.5-7c-0.3-0.3-0.4-0.7-0.3-1.1c0.1-0.4,0.5-0.7,0.9-0.7l11-0.8l4.1-10.1c0.2-0.4,0.5-0.6,0.9-0.6s0.8,0.2,0.9,0.6l4.1,10.1
+ l11,0.8c0.4,0,0.8,0.3,0.9,0.7c0.1,0.4,0,0.8-0.3,1.1l-8.5,7l2.7,10.5c0.1,0.4,0,0.8-0.4,1.1C174.3,71.9,174.1,72,173.9,72z
+ M164,63.9c0.2,0,0.4,0,0.5,0.1l7.8,4.8l-2.2-8.8c-0.1-0.4,0-0.8,0.3-1l7-5.8l-9.1-0.6c-0.4,0-0.7-0.3-0.9-0.6l-3.4-8.4
+ l-3.4,8.4c-0.1,0.4-0.5,0.6-0.9,0.6l-9.1,0.6l7,5.8c0.3,0.2,0.4,0.6,0.3,1l-2.2,8.8l7.8-4.8C163.6,63.9,163.8,63.9,164,63.9z"/>
+ </g>
+ <g>
+ <rect x="124" y="16" class="st0" width="80" height="80"/>
+ </g>
+ </g>
+ <g>
+ <circle class="st0" cx="107" cy="317" r="47"/>
+ <g>
+ <path class="st5" d="M107,363c-11.8,0-23.6-4.5-32.5-13.5c-17.9-17.9-17.9-47.1,0-65.1c17.9-17.9,47.1-17.9,65.1,0
+ c17.9,17.9,17.9,47.1,0,65.1l0,0C130.6,358.5,118.8,363,107,363z"/>
+ </g>
+ <path class="st8" d="M153,317c0-25.4-20.6-46-46-46c-12.7,0-24.2,5.1-32.5,13.5l65.1,65.1C147.9,341.2,153,329.7,153,317z"/>
+ <g>
+ <path class="st5" d="M107,335c-4.8,0-9.3-1.9-12.7-5.3c-7-7-7-18.4,0-25.5c3.4-3.4,7.9-5.3,12.7-5.3s9.3,1.9,12.7,5.3
+ c7,7,7,18.4,0,25.5C116.3,333.1,111.8,335,107,335z"/>
+ <path class="st1" d="M122.2,329.9c6.7-7.9,6.3-19.6-1.1-27.1c-3.9-3.9-9-5.9-14.1-5.9s-10.2,2-14.1,5.9
+ c-7.8,7.8-7.8,20.5,0,28.3c3.9,3.9,9,5.9,14.1,5.9c4.4,0,8.8-1.4,12.4-4.3l18.5,18.5c1-0.9,1.9-1.8,2.8-2.8L122.2,329.9z
+ M95.7,328.3c-6.2-6.2-6.2-16.4,0-22.6c3-3,7-4.7,11.3-4.7s8.3,1.7,11.3,4.7c6.2,6.2,6.2,16.4,0,22.6c-3,3-7,4.7-11.3,4.7
+ S98.7,331.3,95.7,328.3z"/>
+ </g>
+ </g>
+ <g>
+ <circle class="st0" cx="232" cy="317" r="47"/>
+ <g>
+ <path class="st2" d="M232,363c-11.8,0-23.6-4.5-32.5-13.5c-17.9-17.9-17.9-47.1,0-65.1c17.9-17.9,47.1-17.9,65.1,0
+ c17.9,17.9,17.9,47.1,0,65.1l0,0C255.6,358.5,243.8,363,232,363z"/>
+ </g>
+ <path class="st9" d="M278,317c0-25.4-20.6-46-46-46c-12.7,0-24.2,5.1-32.5,13.5l65.1,65.1C272.9,341.2,278,329.7,278,317z"/>
+ <g>
+ <path class="st2" d="M232,335c-4.8,0-9.3-1.9-12.7-5.3c-7-7-7-18.4,0-25.5c3.4-3.4,7.9-5.3,12.7-5.3s9.3,1.9,12.7,5.3
+ c7,7,7,18.4,0,25.5C241.3,333.1,236.8,335,232,335z"/>
+ <path class="st1" d="M247.2,329.9c6.7-7.9,6.3-19.6-1.1-27.1c-3.9-3.9-9-5.9-14.1-5.9s-10.2,2-14.1,5.9
+ c-7.8,7.8-7.8,20.5,0,28.3c3.9,3.9,9,5.9,14.1,5.9c4.4,0,8.8-1.4,12.4-4.3l18.5,18.5c1-0.9,1.9-1.8,2.8-2.8L247.2,329.9z
+ M220.7,328.3c-6.2-6.2-6.2-16.4,0-22.6c3-3,7-4.7,11.3-4.7s8.3,1.7,11.3,4.7c6.2,6.2,6.2,16.4,0,22.6c-3,3-7,4.7-11.3,4.7
+ S223.7,331.3,220.7,328.3z"/>
+ </g>
+ </g>
+ </g>
+</g>
+</svg>
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/about_blank.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/about_blank.png
new file mode 100644
index 0000000..6901b0b
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/about_blank.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/qt.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/qt.png
new file mode 100644
index 0000000..6a22d2e
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/qt.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/touchpoint.png b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/touchpoint.png
new file mode 100644
index 0000000..7649ee9
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/qml/assets/icons/touchpoint.png
Binary files differ
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/resources.qrc b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/resources.qrc
new file mode 100644
index 0000000..451a67f
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/resources.qrc
@@ -0,0 +1,37 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/BrowserWindow.qml</file>
+ <file>qml/FeaturePermissionBar.qml</file>
+ <file>qml/MockTouchPoint.qml</file>
+ <file>qml/PageView.qml</file>
+ <file>qml/NavigationBar.qml</file>
+ <file>qml/HomeScreen.qml</file>
+ <file>qml/SettingsView.qml</file>
+ <file>qml/assets/UIButton.qml</file>
+ <file>qml/assets/UIToolBar.qml</file>
+ <file>qml/Utils.js</file>
+ <file>qml/assets/icons/Btn_Home.png</file>
+ <file>qml/assets/icons/Btn_Tabs.png</file>
+ <file>qml/assets/icons/Btn_Forward.png</file>
+ <file>qml/assets/icons/Btn_Back.png</file>
+ <file>qml/assets/icons/Btn_Reload.png</file>
+ <file>qml/assets/icons/Btn_Clear.png</file>
+ <file>qml/assets/icons/touchpoint.png</file>
+ <file>qml/assets/icons/Btn_Delete.png</file>
+ <file>qml/assets/icons/Btn_Bookmarks.png</file>
+ <file>qml/assets/icons/Btn_Bookmark_Checked.png</file>
+ <file>qml/assets/icons/Btn_Bookmark_Indicator.png</file>
+ <file>qml/assets/icons/Btn_Settings.png</file>
+ <file>qml/assets/icons/about_blank.png</file>
+ <file>qml/assets/icons/Btn_Add.png</file>
+ <file>qml/assets/icons/Btn_Up.png</file>
+ <file>qml/assets/icons/Btn_Search.png</file>
+ <file>qml/assets/icons/Error_Icon.png</file>
+ <file>qml/assets/icons/qt.png</file>
+ <file>qml/assets/icons/AppLogoColor.png</file>
+ <file>qml/assets/icons/AppLogoGrey.png</file>
+ <file>qml/Keyboard.qml</file>
+ <file>qml/Main.qml</file>
+ <file>qml/CustomSwitch.qml</file>
+ </qresource>
+</RCC>
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/src.pro b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/src.pro
new file mode 100644
index 0000000..79e4bdc
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/src.pro
@@ -0,0 +1,43 @@
+TARGET = qtwebbrowser
+
+CONFIG += c++11
+CONFIG -= app_bundle
+
+SOURCES = \
+ appengine.cpp \
+ main.cpp \
+ navigationhistoryproxymodel.cpp \
+ touchtracker.cpp
+
+HEADERS = \
+ appengine.h \
+ navigationhistoryproxymodel.h \
+ touchtracker.h \
+
+OTHER_FILES = \
+ qml/assets/UIButton.qml \
+ qml/assets/UIToolBar.qml \
+ qml/ApplicationRoot.qml \
+ qml/BrowserWindow.qml \
+ qml/FeaturePermissionBar.qml \
+ qml/MockTouchPoint.qml \
+ qml/PageView.qml \
+ qml/NavigationBar.qml \
+ qml/HomeScreen.qml \
+ qml/SettingsView.qml \
+ qml/Keyboard.qml \
+ qml/Window.qml
+
+QT += qml quick webengine
+
+RESOURCES += resources.qrc
+
+!cross_compile {
+ DEFINES += DESKTOP_BUILD
+ SOURCES += touchmockingapplication.cpp
+ HEADERS += touchmockingapplication.h
+ QT += gui-private
+} else {
+ target.path =/data/user/qt/qtwebbrowser-app
+ INSTALLS += target
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchmockingapplication.cpp b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchmockingapplication.cpp
new file mode 100644
index 0000000..256c45e
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchmockingapplication.cpp
@@ -0,0 +1,282 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "touchmockingapplication.h"
+#include "appengine.h"
+
+#include <qpa/qwindowsysteminterface.h>
+#include <QtCore/QRegExp>
+#include <QtCore/QEvent>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QTouchEvent>
+#include <QtQuick/QQuickItem>
+#include <QtQuick/QQuickView>
+
+using namespace utils;
+
+static inline QRectF touchRectForPosition(QPointF centerPoint)
+{
+ QRectF touchRect(0, 0, 40, 40);
+ touchRect.moveCenter(centerPoint);
+ return touchRect;
+}
+
+TouchMockingApplication::TouchMockingApplication(int& argc, char** argv)
+ : QGuiApplication(argc, argv)
+ , m_realTouchEventReceived(false)
+ , m_pendingFakeTouchEventCount(0)
+ , m_holdingControl(false)
+{
+}
+
+bool TouchMockingApplication::notify(QObject* target, QEvent* event)
+{
+ // We try to be smart, if we received real touch event, we are probably on a device
+ // with touch screen, and we should not have touch mocking.
+
+ if (!event->spontaneous() || m_realTouchEventReceived)
+ return QGuiApplication::notify(target, event);
+
+ if (isTouchEvent(event)) {
+ if (m_pendingFakeTouchEventCount)
+ --m_pendingFakeTouchEventCount;
+ else
+ m_realTouchEventReceived = true;
+ return QGuiApplication::notify(target, event);
+ }
+
+ QQuickView* window = qobject_cast<QQuickView*>(target);
+ if (!window)
+ return QGuiApplication::notify(target, event);
+
+ m_holdingControl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
+
+ if (event->type() == QEvent::KeyRelease && static_cast<QKeyEvent*>(event)->key() == Qt::Key_Control) {
+ foreach (int id, m_heldTouchPoints)
+ if (m_touchPoints.contains(id) && !QGuiApplication::mouseButtons().testFlag(Qt::MouseButton(id))) {
+ m_touchPoints[id].setState(Qt::TouchPointReleased);
+ m_heldTouchPoints.remove(id);
+ } else
+ m_touchPoints[id].setState(Qt::TouchPointStationary);
+
+ sendTouchEvent(window, m_heldTouchPoints.isEmpty() ? QEvent::TouchEnd : QEvent::TouchUpdate, static_cast<QKeyEvent*>(event)->timestamp());
+ }
+
+ if (isMouseEvent(event)) {
+ const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
+
+ QTouchEvent::TouchPoint touchPoint;
+ touchPoint.setPressure(1);
+
+ QEvent::Type touchType = QEvent::None;
+
+ switch (mouseEvent->type()) {
+ case QEvent::MouseButtonPress:
+ touchPoint.setId(mouseEvent->button());
+ if (m_touchPoints.contains(touchPoint.id())) {
+ touchPoint.setState(Qt::TouchPointMoved);
+ touchType = QEvent::TouchUpdate;
+ } else {
+ touchPoint.setState(Qt::TouchPointPressed);
+ // Check if more buttons are held down than just the event triggering one.
+ if (mouseEvent->buttons() > mouseEvent->button())
+ touchType = QEvent::TouchUpdate;
+ else
+ touchType = QEvent::TouchBegin;
+ }
+ break;
+ case QEvent::MouseMove:
+ if (!mouseEvent->buttons()) {
+ // We have to swallow the event instead of propagating it,
+ // since we avoid sending the mouse release events and if the
+ // Flickable is the mouse grabber it would receive the event
+ // and would move the content.
+ event->accept();
+ return true;
+ }
+ touchType = QEvent::TouchUpdate;
+ touchPoint.setId(mouseEvent->buttons());
+ touchPoint.setState(Qt::TouchPointMoved);
+ break;
+ case QEvent::MouseButtonRelease:
+ // Check if any buttons are still held down after this event.
+ if (mouseEvent->buttons())
+ touchType = QEvent::TouchUpdate;
+ else
+ touchType = QEvent::TouchEnd;
+ touchPoint.setId(mouseEvent->button());
+ touchPoint.setState(Qt::TouchPointReleased);
+ break;
+ case QEvent::MouseButtonDblClick:
+ // Eat double-clicks, their accompanying press event is all we need.
+ event->accept();
+ return true;
+ default:
+ Q_ASSERT_X(false, "multi-touch mocking", "unhandled event type");
+ }
+
+ // A move can have resulted in multiple buttons, so we need check them individually.
+ if (touchPoint.id() & Qt::LeftButton)
+ updateTouchPoint(mouseEvent, touchPoint, Qt::LeftButton);
+ if (touchPoint.id() & Qt::MidButton)
+ updateTouchPoint(mouseEvent, touchPoint, Qt::MidButton);
+ if (touchPoint.id() & Qt::RightButton)
+ updateTouchPoint(mouseEvent, touchPoint, Qt::RightButton);
+
+ if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) {
+ // We avoid sending the release event because the Flickable is
+ // listening to mouse events and would start a bounce-back
+ // animation if it received a mouse release.
+ event->accept();
+ return true;
+ }
+
+ // Update states for all other touch-points
+ for (QHash<int, QTouchEvent::TouchPoint>::iterator it = m_touchPoints.begin(), end = m_touchPoints.end(); it != end; ++it) {
+ if (!(it.value().id() & touchPoint.id()))
+ it.value().setState(Qt::TouchPointStationary);
+ }
+
+ Q_ASSERT(touchType != QEvent::None);
+
+ if (!sendTouchEvent(window, touchType, mouseEvent->timestamp()))
+ return QGuiApplication::notify(target, event);
+
+ event->accept();
+ return true;
+ }
+
+ return QGuiApplication::notify(target, event);
+}
+
+void TouchMockingApplication::updateTouchPoint(const QMouseEvent* mouseEvent, QTouchEvent::TouchPoint touchPoint, Qt::MouseButton mouseButton)
+{
+ // Ignore inserting additional touch points if Ctrl isn't held because it produces
+ // inconsistent touch events and results in assers in the gesture recognizers.
+ if (!m_holdingControl && m_touchPoints.size() && !m_touchPoints.contains(mouseButton))
+ return;
+
+ if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) {
+ m_heldTouchPoints.insert(mouseButton);
+ return;
+ }
+
+ // Gesture recognition uses the screen position for the initial threshold
+ // but since the canvas translates touch events we actually need to pass
+ // the screen position as the scene position to deliver the appropriate
+ // coordinates to the target.
+ touchPoint.setRect(touchRectForPosition(mouseEvent->localPos()));
+ touchPoint.setSceneRect(touchRectForPosition(mouseEvent->screenPos()));
+
+ if (touchPoint.state() == Qt::TouchPointPressed)
+ touchPoint.setStartScenePos(mouseEvent->screenPos());
+ else {
+ const QTouchEvent::TouchPoint& oldTouchPoint = m_touchPoints[mouseButton];
+ touchPoint.setStartScenePos(oldTouchPoint.startScenePos());
+ touchPoint.setLastPos(oldTouchPoint.pos());
+ touchPoint.setLastScenePos(oldTouchPoint.scenePos());
+ }
+
+ // Update current touch-point.
+ touchPoint.setId(mouseButton);
+ m_touchPoints.insert(mouseButton, touchPoint);
+}
+
+bool TouchMockingApplication::sendTouchEvent(QQuickView* window, QEvent::Type type, ulong timestamp)
+{
+ static QTouchDevice* device = 0;
+ if (!device) {
+ device = new QTouchDevice;
+ device->setType(QTouchDevice::TouchScreen);
+ QWindowSystemInterface::registerTouchDevice(device);
+ }
+
+ m_pendingFakeTouchEventCount++;
+
+ const QList<QTouchEvent::TouchPoint>& currentTouchPoints = m_touchPoints.values();
+ Qt::TouchPointStates touchPointStates = 0;
+ foreach (const QTouchEvent::TouchPoint& touchPoint, currentTouchPoints)
+ touchPointStates |= touchPoint.state();
+
+ QTouchEvent event(type, device, Qt::NoModifier, touchPointStates, currentTouchPoints);
+ event.setTimestamp(timestamp);
+ event.setAccepted(false);
+
+ QGuiApplication::notify(window, &event);
+
+ updateVisualMockTouchPoints(window,m_holdingControl ? currentTouchPoints : QList<QTouchEvent::TouchPoint>());
+
+ // Get rid of touch-points that are no longer valid
+ foreach (const QTouchEvent::TouchPoint& touchPoint, currentTouchPoints) {
+ if (touchPoint.state() == Qt::TouchPointReleased)
+ m_touchPoints.remove(touchPoint.id());
+ }
+
+ return event.isAccepted();
+}
+
+void TouchMockingApplication::updateVisualMockTouchPoints(QQuickView* window,const QList<QTouchEvent::TouchPoint>& touchPoints)
+{
+ if (touchPoints.isEmpty()) {
+ // Hide all touch indicator items.
+ foreach (QQuickItem* item, m_activeMockComponents.values())
+ item->setProperty("pressed", false);
+
+ return;
+ }
+
+ foreach (const QTouchEvent::TouchPoint& touchPoint, touchPoints) {
+ QQuickItem* mockTouchPointItem = m_activeMockComponents.value(touchPoint.id());
+
+ if (!mockTouchPointItem) {
+ QQmlComponent touchMockPointComponent(window->engine(), QUrl("qrc:///qml/MockTouchPoint.qml"));
+ mockTouchPointItem = qobject_cast<QQuickItem*>(touchMockPointComponent.create());
+ Q_ASSERT(mockTouchPointItem);
+ m_activeMockComponents.insert(touchPoint.id(), mockTouchPointItem);
+ mockTouchPointItem->setProperty("pointId", QVariant(touchPoint.id()));
+ mockTouchPointItem->setParent(window->rootObject());
+ mockTouchPointItem->setParentItem(window->rootObject());
+ }
+
+ QRectF touchRect = touchPoint.rect();
+ mockTouchPointItem->setX(touchRect.center().x());
+ mockTouchPointItem->setY(touchRect.center().y());
+ mockTouchPointItem->setWidth(touchRect.width());
+ mockTouchPointItem->setHeight(touchRect.height());
+ mockTouchPointItem->setProperty("pressed", QVariant(touchPoint.state() != Qt::TouchPointReleased));
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchmockingapplication.h b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchmockingapplication.h
new file mode 100644
index 0000000..88c5e20
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchmockingapplication.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TOUCHMOCKINGAPPLICATION_H
+#define TOUCHMOCKINGAPPLICATION_H
+
+#include <QtCore/QHash>
+#include <QtCore/QUrl>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QTouchEvent>
+
+QT_BEGIN_NAMESPACE
+class QQuickView;
+class QQuickItem;
+QT_END_NAMESPACE
+
+class TouchMockingApplication : public QGuiApplication
+{
+ Q_OBJECT
+
+public:
+ TouchMockingApplication(int &argc, char **argv);
+
+ virtual bool notify(QObject *, QEvent *) override;
+
+private:
+ void updateTouchPoint(const QMouseEvent *, QTouchEvent::TouchPoint, Qt::MouseButton);
+ bool sendTouchEvent(QQuickView *, QEvent::Type, ulong timestamp);
+ void updateVisualMockTouchPoints(QQuickView *,const QList<QTouchEvent::TouchPoint> &touchPoints);
+
+private:
+ bool m_realTouchEventReceived;
+ int m_pendingFakeTouchEventCount;
+
+ QPointF m_lastPos;
+ QPointF m_lastScreenPos;
+ QPointF m_startScreenPos;
+
+ QHash<int, QTouchEvent::TouchPoint> m_touchPoints;
+ QSet<int> m_heldTouchPoints;
+ QHash<int, QQuickItem*> m_activeMockComponents;
+
+ bool m_holdingControl;
+};
+
+#endif // TOUCHMOCKINGAPPLICATION_H
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchtracker.cpp b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchtracker.cpp
new file mode 100644
index 0000000..52a028a
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchtracker.cpp
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "touchtracker.h"
+#include "appengine.h"
+
+#include <QDateTime>
+
+using namespace utils;
+
+TouchTracker::TouchTracker(QQuickItem *parent)
+ : QQuickItem(parent)
+ , m_blockEvents(false)
+ , m_diff(0)
+ , m_previousY(0)
+ , m_target(0)
+ , m_delegate(0)
+{
+ m_startPoint.ts = 0;
+ m_currentPoint.ts = 0;
+}
+
+QQuickItem *TouchTracker::target() const
+{
+ return m_target;
+}
+
+void TouchTracker::setTarget(QQuickItem * target)
+{
+ m_target = target;
+ emit targetChanged();
+}
+
+int TouchTracker::xVelocity() const
+{
+ qreal pos = qAbs(m_startPoint.x() - m_currentPoint.x());
+ qreal time = qAbs(m_startPoint.ts - m_currentPoint.ts);
+ return pos / time * 1000;
+}
+
+int TouchTracker::yVelocity() const
+{
+ qreal pos = qAbs(m_startPoint.y() - m_currentPoint.y());
+ qreal time = qAbs(m_startPoint.ts - m_currentPoint.ts);
+ return pos / time * 1000;
+}
+
+
+qreal TouchTracker::touchX() const
+{
+ return m_currentPoint.x();
+}
+
+qreal TouchTracker::touchY() const
+{
+ return m_currentPoint.y();
+}
+
+bool TouchTracker::blockEvents() const
+{
+ return m_blockEvents;
+}
+
+void TouchTracker::setBlockEvents(bool shouldBlock)
+{
+ if (m_blockEvents == shouldBlock)
+ return;
+ m_blockEvents = shouldBlock;
+ emit blockEventsChanged();
+}
+
+bool TouchTracker::eventFilter(QObject *obj, QEvent *event)
+{
+ if (obj != m_delegate)
+ return QQuickItem::eventFilter(obj, event);
+
+ if (event->type() == QEvent::Wheel)
+ return m_blockEvents;
+
+ if (!isTouchEvent(event))
+ return QQuickItem::eventFilter(obj, event);
+
+ const QTouchEvent *touch = static_cast<QTouchEvent*>(event);
+ const QList<QTouchEvent::TouchPoint> &points = touch->touchPoints();
+ m_previousY = m_currentPoint.y();
+ m_currentPoint.pos = m_target->mapToScene(points.at(0).pos());
+ m_currentPoint.ts = QDateTime::currentMSecsSinceEpoch();
+ int currentDiff = m_previousY - m_currentPoint.y();
+
+ if ((currentDiff > 0 && m_diff < 0) || (currentDiff < 0 && m_diff > 0))
+ emit scrollDirectionChanged();
+
+ m_diff = currentDiff;
+
+ emit touchChanged();
+ emit velocityChanged();
+
+ if (event->type() == QEvent::TouchEnd)
+ emit touchEnd();
+
+ return m_blockEvents;
+}
+
+void TouchTracker::touchEvent(QTouchEvent * event)
+{
+ if (!m_target) {
+ if (!m_blockEvents)
+ QQuickItem::touchEvent(event);
+
+ return;
+ }
+
+ event->setAccepted(false);
+
+ const QList<QTouchEvent::TouchPoint> &points = event->touchPoints();
+ m_currentPoint.pos = m_target->mapToScene(points.at(0).pos());
+ m_currentPoint.ts = QDateTime::currentMSecsSinceEpoch();
+
+ if (event->type() == QEvent::TouchBegin) {
+ m_startPoint = m_currentPoint;
+ emit touchBegin();
+ }
+
+ emit touchChanged();
+
+ // We have to find the delegate to be able to filter
+ // events from the WebEngineView.
+ // This is a hack and should preferably be made easier
+ // with the API in some way.
+ QQuickItem *child = m_target->childAt(m_currentPoint.x(), m_currentPoint.y());
+ if (child && m_delegate != child) {
+ child->installEventFilter(this);
+ m_delegate = child;
+ }
+}
diff --git a/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchtracker.h b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchtracker.h
new file mode 100644
index 0000000..3aaecd2
--- /dev/null
+++ b/basicsuite/qtwebbrowser/tqtc-qtwebbrowser/src/touchtracker.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBrowser project.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPLv2 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TOUCHTRACKER_H
+#define TOUCHTRACKER_H
+
+#include <QObject>
+#include <QQuickItem>
+
+class TouchTracker : public QQuickItem
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal touchX READ touchX NOTIFY touchChanged)
+ Q_PROPERTY(qreal touchY READ touchY NOTIFY touchChanged)
+ Q_PROPERTY(int xVelocity READ xVelocity NOTIFY velocityChanged)
+ Q_PROPERTY(int yVelocity READ yVelocity NOTIFY velocityChanged)
+ Q_PROPERTY(bool blockEvents READ blockEvents WRITE setBlockEvents NOTIFY blockEventsChanged)
+ Q_PROPERTY(QQuickItem* target READ target WRITE setTarget NOTIFY targetChanged)
+
+ struct PositionInfo
+ {
+ QPointF pos;
+ qint64 ts;
+ qreal x() const { return pos.x(); }
+ qreal y() const { return pos.y(); }
+ };
+
+public:
+ TouchTracker(QQuickItem *parent = 0);
+
+ qreal touchX() const;
+ qreal touchY() const;
+ int xVelocity() const;
+ int yVelocity() const;
+ QQuickItem* target() const;
+ bool blockEvents() const;
+ void setBlockEvents(bool shouldBlock);
+ void setTarget(QQuickItem * target);
+
+signals:
+ void touchChanged();
+ void blockEventsChanged();
+ void targetChanged();
+ void touchBegin();
+ void touchEnd();
+ void velocityChanged();
+ void scrollDirectionChanged();
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *event);
+ void touchEvent(QTouchEvent *event) override;
+
+private:
+ bool m_blockEvents;
+ int m_diff;
+ int m_previousY;
+ PositionInfo m_startPoint;
+ PositionInfo m_currentPoint;
+ QQuickItem *m_target;
+ QQuickItem *m_delegate;
+};
+
+#endif // TOUCHTRACKER_H
diff --git a/basicsuite/shared/fonts.qrc b/basicsuite/shared/fonts.qrc
new file mode 100644
index 0000000..13b8cb2
--- /dev/null
+++ b/basicsuite/shared/fonts.qrc
@@ -0,0 +1,15 @@
+<RCC>
+ <qresource prefix="/">
+ <file>fonts/TitilliumWeb-Black.ttf</file>
+ <file>fonts/TitilliumWeb-Bold.ttf</file>
+ <file>fonts/TitilliumWeb-BoldItalic.ttf</file>
+ <file>fonts/TitilliumWeb-ExtraLight.ttf</file>
+ <file>fonts/TitilliumWeb-ExtraLightItalic.ttf</file>
+ <file>fonts/TitilliumWeb-Italic.ttf</file>
+ <file>fonts/TitilliumWeb-Light.ttf</file>
+ <file>fonts/TitilliumWeb-LightItalic.ttf</file>
+ <file>fonts/TitilliumWeb-Regular.ttf</file>
+ <file>fonts/TitilliumWeb-SemiBold.ttf</file>
+ <file>fonts/TitilliumWeb-SemiBoldItalic.ttf</file>
+ </qresource>
+</RCC>
diff --git a/basicsuite/shared/fonts/TitilliumWeb-Black.ttf b/basicsuite/shared/fonts/TitilliumWeb-Black.ttf
new file mode 100644
index 0000000..fc5c4b5
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-Black.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-Bold.ttf b/basicsuite/shared/fonts/TitilliumWeb-Bold.ttf
new file mode 100644
index 0000000..0af0fe7
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-Bold.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-BoldItalic.ttf b/basicsuite/shared/fonts/TitilliumWeb-BoldItalic.ttf
new file mode 100644
index 0000000..77425ea
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-BoldItalic.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-ExtraLight.ttf b/basicsuite/shared/fonts/TitilliumWeb-ExtraLight.ttf
new file mode 100644
index 0000000..2b506ef
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-ExtraLight.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-ExtraLightItalic.ttf b/basicsuite/shared/fonts/TitilliumWeb-ExtraLightItalic.ttf
new file mode 100644
index 0000000..c1be5ba
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-ExtraLightItalic.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-Italic.ttf b/basicsuite/shared/fonts/TitilliumWeb-Italic.ttf
new file mode 100644
index 0000000..42f2c10
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-Italic.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-Light.ttf b/basicsuite/shared/fonts/TitilliumWeb-Light.ttf
new file mode 100644
index 0000000..ca67971
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-Light.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-LightItalic.ttf b/basicsuite/shared/fonts/TitilliumWeb-LightItalic.ttf
new file mode 100644
index 0000000..2ea724f
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-LightItalic.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-Regular.ttf b/basicsuite/shared/fonts/TitilliumWeb-Regular.ttf
new file mode 100644
index 0000000..6da8219
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-Regular.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-SemiBold.ttf b/basicsuite/shared/fonts/TitilliumWeb-SemiBold.ttf
new file mode 100644
index 0000000..dfdcdbe
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-SemiBold.ttf
Binary files differ
diff --git a/basicsuite/shared/fonts/TitilliumWeb-SemiBoldItalic.ttf b/basicsuite/shared/fonts/TitilliumWeb-SemiBoldItalic.ttf
new file mode 100644
index 0000000..b68a669
--- /dev/null
+++ b/basicsuite/shared/fonts/TitilliumWeb-SemiBoldItalic.ttf
Binary files differ
diff --git a/basicsuite/shared/main.cpp b/basicsuite/shared/main.cpp
index 3c21bf5..4afbbe4 100644
--- a/basicsuite/shared/main.cpp
+++ b/basicsuite/shared/main.cpp
@@ -63,6 +63,9 @@
#include <QtQml/QQmlEngine>
#include <QtQml/QQmlContext>
#include <QtQml/QQmlComponent>
+#include <QSettings>
+#include <QQuickStyle>
+#include <QIcon>
#if defined(USE_QTWEBENGINE)
#include <qtwebengineglobal.h>
@@ -73,10 +76,12 @@
int main(int argc, char **argv)
{
//qputenv("QT_IM_MODULE", QByteArray("qtvkb"));
-
+ qputenv("QT_QUICK_CONTROLS_CONF", "/data/user/qt/qtquickcontrols2/qtquickcontrols2.conf");
+ QIcon::setThemeName("gallery");
+ QIcon::setThemeSearchPaths(QStringList() << "/data/user/qt/qtquickcontrols2/icons");
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
-
#if defined(USE_QTWEBENGINE)
// This is currently needed by all QtWebEngine applications using the HW accelerated QQuickWebView.
// It enables sharing the QOpenGLContext of all QQuickWindows of the application.
@@ -84,6 +89,22 @@ int main(int argc, char **argv)
QtWebEngine::initialize();
#endif
+ QFontDatabase::addApplicationFont(":/fonts/TitilliumWeb-Regular.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/TitilliumWeb-SemiBold.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/TitilliumWeb-Bold.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/TitilliumWeb-Black.ttf");
+
+ //For eBike demo
+ QFontDatabase::addApplicationFont(":/fonts/Montserrat-Bold.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/Montserrat-Light.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/Montserrat-Medium.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/Montserrat-Regular.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/Teko-Bold.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/Teko-Light.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/Teko-Medium.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/Teko-Regular.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/fontawesome-webfont.ttf");
+
QString path = app.applicationDirPath();
QPalette pal;
@@ -114,10 +135,36 @@ int main(int argc, char **argv)
QGuiApplication::setFont(font);
}
+ QString videosPath = QStringLiteral("file://");
+ QString defaultVideoUrl = QStringLiteral("file:///data/videos/Qt_video_720p.webm");
+ videosPath.append("/data/videos");
+
+ QSettings styleSettings;
+ QString style = styleSettings.value("style").toString();
+ if (style.isEmpty() || style == "Default")
+ styleSettings.setValue("style", "Material");
+ QQuickStyle::setStyle(styleSettings.value("style").toString());
+
DummyEngine engine;
QQmlApplicationEngine applicationengine;
+ QString appFont("TitilliumWeb");
applicationengine.rootContext()->setContextProperty("engine", &engine);
+ applicationengine.rootContext()->setContextProperty("appFont", appFont);
+ applicationengine.rootContext()->setContextProperty("availableStyles", QQuickStyle::availableStyles());
+
+ applicationengine.rootContext()->setContextProperty("VideosLocation", videosPath);
+ applicationengine.rootContext()->setContextProperty("DefaultVideoUrl", defaultVideoUrl);
+
+ QSettings themeColorSettings("QtLauncher", "colorSettings");
+
+ applicationengine.rootContext()->setContextProperty("_backgroundColor", themeColorSettings.value("backgroundColor", "#09102b"));
+ applicationengine.rootContext()->setContextProperty("_primaryGreen", themeColorSettings.value("primaryGreen", "#41cd52"));
+ applicationengine.rootContext()->setContextProperty("_mediumGreen", themeColorSettings.value("mediumGreen", "#21be2b"));
+ applicationengine.rootContext()->setContextProperty("_darkGreen", themeColorSettings.value("darkGreen", "#17a81a"));
+ applicationengine.rootContext()->setContextProperty("_primaryGrey", themeColorSettings.value("primaryGrey", "#9d9faa"));
+ applicationengine.rootContext()->setContextProperty("_secondaryGrey", themeColorSettings.value("secondaryGrey", "#3a4055"));
+
applicationengine.load(QUrl::fromLocalFile(path + "/SharedMain.qml"));
app.exec();
diff --git a/basicsuite/shared/settings.js b/basicsuite/shared/settings.js
new file mode 100644
index 0000000..8dcc976
--- /dev/null
+++ b/basicsuite/shared/settings.js
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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
+
+var backgroundColor = "#09102b"
+
+var primaryGreen = "#41cd52"
+var mediumGreen = "#21be2b"
+var darkGreen = "#17a81a"
+
+var primaryGrey = "#9d9faa"
+var secondaryGrey = "#3a4055"
+
diff --git a/basicsuite/shared/settings.qrc b/basicsuite/shared/settings.qrc
new file mode 100644
index 0000000..576a1e9
--- /dev/null
+++ b/basicsuite/shared/settings.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>settings.js</file>
+ </qresource>
+</RCC>
diff --git a/basicsuite/shared/shared.pri b/basicsuite/shared/shared.pri
index bc1c222..223bb02 100644
--- a/basicsuite/shared/shared.pri
+++ b/basicsuite/shared/shared.pri
@@ -1,12 +1,12 @@
# widget dependecy is required by QtCharts demo
-QT += quick widgets
+QT += quick widgets quickcontrols2
qtHaveModule(webengine) {
DEFINES += USE_QTWEBENGINE
QT += webengine
}
-DESTPATH = /data/user/$$TARGET
+DESTPATH = /data/user/qt/$$TARGET
target.path = $$DESTPATH
SOURCES += $$PWD/main.cpp \
@@ -25,3 +25,18 @@ defineTest(b2qtdemo_deploy_defaults) {
export(OTHER_FILES)
export(INSTALLS)
}
+
+DISTFILES += $$PWD/fonts/TitilliumWeb-Black.ttf \
+ $$PWD/fonts/TitilliumWeb-Bold.ttf \
+ $$PWD/fonts/TitilliumWeb-BoldItalic.ttf \
+ $$PWD/fonts/TitilliumWeb-ExtraLight.ttf \
+ $$PWD/fonts/TitilliumWeb-ExtraLightItalic.ttf \
+ $$PWD/fonts/TitilliumWeb-Italic.ttf \
+ $$PWD/fonts/TitilliumWeb-Light.ttf \
+ $$PWD/fonts/TitilliumWeb-LightItalic.ttf \
+ $$PWD/fonts/TitilliumWeb-Regular.ttf \
+ $$PWD/fonts/TitilliumWeb-SemiBold.ttf \
+ $$PWD/fonts/TitilliumWeb-SemiBoldItalic.ttf
+
+RESOURCES += \
+ $$PWD/fonts.qrc
diff --git a/doc/b2qt-demos.qdoc b/doc/b2qt-demos.qdoc
index 2304cc5..687e374 100644
--- a/doc/b2qt-demos.qdoc
+++ b/doc/b2qt-demos.qdoc
@@ -50,7 +50,7 @@
/*!
\page index.html
- \title Qt 5.9.7 for Device Creation Examples and Demos
+ \title Qt 5.11.2 for Device Creation Examples and Demos
\SDK has a number of examples and demos. These are included in the
\B2Q images, available in the launcher that is run by default at
@@ -63,32 +63,20 @@
*/
/*!
- \example basicsuite/about-b2qt
- \title About Qt for Device Creation
+ \example basicsuite/ebike-ui
+ \title e-bike Demo
\ingroup b2qt-demos
- \brief Displays information about Qt for Device Creation.
+ \brief An E-bike instrument cluster concept.
- \image b2qt-demo-about-b2qt.jpg
+ \image b2qt-demo-ebike-ui.jpg
- A demo that provides an introduction to what Qt for Device Creation is all about.
-*/
-
-/*!
- \example basicsuite/camera
- \title Camera
- \ingroup b2qt-demos
- \brief Demonstrates using camera in a QML application.
-
- \image b2qt-demo-camera.jpg
+ An E-bike instrument cluster concept designed and implemented by Qt.
- This example demonstrates the use of the camera features provided by Qt Multimedia with
- Qt Quick.
-
- Demo can be used to take pictures. Files are saved inside the \c {/data/images/} folder
- and can be then viewed with the \l {Photo Gallery} demo application.
+ The entire concept is a testament that Qt brings the HMI designers and Software engineers together
+ by using the same tools, allowing them to fast prototyping through collaborative workflow.
+ In addition to that, Qt is optimized for running on low end SoC even the ones that don't have GPU acceleration
+ for graphics.
- Camera parameters such as flash mode, scene mode or white balance can be changed. The
- availability of parameters depends on what the camera driver provides.
*/
/*!
@@ -120,40 +108,6 @@
*/
/*!
- \example basicsuite/canvas3d-planets
- \title Qt Canvas3D Planets
- \ingroup b2qt-demos
- \brief Demonstrates the usage of Qt Canvas 3D, a WebGL-like API for QML, and popular JavaScript frameworks like three.js.
-
- \image b2qt-demo-canvas3d-planets.jpg
-
- The demo uses Qt Quick and Canvas 3D in combination with \l{http://threejs.org/}{three.js},
- \l{https://github.com/jeromeetienne/threex.planets}{threex.planets}, and Qt Quick
- Controls. Tapping on a planet changes the view and moves to the chosen planet. Use the sliders
- to adjust the zoom levels and viewing distance. Tapping outside the controls moves the focus
- back to the solar view.
-*/
-
-/*!
- \example basicsuite/qt5-cinematicdemo
- \title Qt 5 Cinematic Demo
- \ingroup b2qt-demos
- \brief A cool demonstration of the graphical prowess of Qt 5 and Qt Quick 2.
-
- \image b2qt-demo-qt5-cinematicdemo.jpg
-
- The Qt5 Cinematic Experience is a demo by "QUIt Coding", a small group of talented individuals
- enjoying software development with cutting edge technologies. They are official members of the
- Qt Ambassador Program.
-
- The demo shows off a number features of Qt Quick 2.0. A nicely styled list control of movie
- covers with lighting effects, particles and transitions. The information roll-down curvy
- curtain is implemented using inline GLSL in the QML file.
-
- More awesome looking Qt Quick examples are available from \l {http://quitcoding.com}.
-*/
-
-/*!
\example basicsuite/textinput
\title Text Input
\ingroup b2qt-demos
@@ -181,49 +135,16 @@
*/
/*!
- \example basicsuite/enterprise-dashboard
- \title Dashboard
- \ingroup b2qt-demos
- \brief A car dashboard created using Qt Quick Enterprise Controls.
-
- \image b2qt-demo-enterprise-dashboard.jpg
-
- This example project demonstrates using several CircularGauge controls to create a car dashboard.
-*/
-
-/*!
- \example basicsuite/enterprise-qtdatavis3d
- \title Qt Data Visualization
- \ingroup b2qt-demos
- \brief An interactive showcase for Qt Data Visualization 3D.
-
- \image b2qt-demo-enterprise-qtdatavis3d.jpg
-
- This example demonstrates how to use the Qt Data Visualization module to display 3D data visualizations in QML.
-*/
-
-/*!
- \example basicsuite/enterprise-gallery
- \title Enterprise Controls Gallery
- \ingroup b2qt-demos
- \brief An interactive showcase for Qt Quick Enterprise Controls.
-
- \image b2qt-demo-enterprise-gallery.jpg
-
- This example project demonstrates the various UI controls provided by Qt Quick Enterprise Controls.
-*/
-
-/*!
- \example basicsuite/enterprise-flat-controls
- \title Enterprise Flat Controls
+ \example basicsuite/qtquickcontrols2
+ \title Qt Quick Controls 2 - Gallery
\ingroup b2qt-demos
- \brief The Flat Style Gallery example showcases the Qt Quick Controls.
+ \brief A gallery of controls.
- \image b2qt-demo-enterprise-flat-controls.jpg
+ \image b2qt-demo-qtquickcontrols2.jpg
- This demo combines both the standard Quick Controls as well as the Qt Quick Enterprise Controls
- both of which are using the new Flat style. The Flat style for Qt Quick Controls has a modern
- look and feel and is perfect for the touch driven interfaces commonly developed by device creators.
+ The gallery example is a simple application with a drawer menu that contains all the Qt Quick Controls 2. Each
+ menu item opens a page that shows the graphical appearance of a control, allows you to interact with the control,
+ and explains in which circumstances it is handy to use this control.
*/
/*!
diff --git a/doc/b2qt-demos.qdocconf b/doc/b2qt-demos.qdocconf
index 1d54d34..907f639 100644
--- a/doc/b2qt-demos.qdocconf
+++ b/doc/b2qt-demos.qdocconf
@@ -5,8 +5,8 @@ outputencoding = UTF-8
sourceencoding = UTF-8
project = QtforDeviceCreationDemos
-description = Qt 5.9.7 for Device Creation Examples and Demos
-version = 5.9.7
+description = Qt 5.11.2 for Device Creation Examples and Demos
+version = 5.11.2
sourcedirs = .
imagedirs += images
@@ -21,9 +21,9 @@ exampledirs = ..
qhp.projects = QtforDeviceCreationDemos
qhp.QtforDeviceCreationDemos.file = b2qt-demos.qhp
-qhp.QtforDeviceCreationDemos.namespace = org.qt-project.b2qt-demos.597
+qhp.QtforDeviceCreationDemos.namespace = org.qt-project.b2qt-demos.5112
qhp.QtforDeviceCreationDemos.virtualFolder = b2qt-demos
-qhp.QtforDeviceCreationDemos.indexTitle = Qt 5.9.7 for Device Creation Examples and Demos
+qhp.QtforDeviceCreationDemos.indexTitle = Qt 5.11.2 for Device Creation Examples and Demos
qhp.QtforDeviceCreationDemos.indexRoot =
qhp.QtforDeviceCreationDemos.subprojects = demos
@@ -35,4 +35,4 @@ manifestmeta.b2qt.names = "QtforDeviceCreationDemos/*"
macro.B2Q = "Boot to Qt"
macro.SDK = "Qt for Device Creation"
-navigation.landingpage = "Qt 5.9.7 for Device Creation Examples and Demos"
+navigation.landingpage = "Qt 5.11.2 for Device Creation Examples and Demos"
diff --git a/doc/images/b2qt-demo-ebike-ui.jpg b/doc/images/b2qt-demo-ebike-ui.jpg
new file mode 100644
index 0000000..570e609
--- /dev/null
+++ b/doc/images/b2qt-demo-ebike-ui.jpg
Binary files differ
diff --git a/doc/images/b2qt-demo-enterprise-charts.jpg b/doc/images/b2qt-demo-enterprise-charts.jpg
index e2f9e6c..b472087 120000..100644
--- a/doc/images/b2qt-demo-enterprise-charts.jpg
+++ b/doc/images/b2qt-demo-enterprise-charts.jpg
Binary files differ
diff --git a/doc/images/b2qt-demo-graphicaleffects.jpg b/doc/images/b2qt-demo-graphicaleffects.jpg
index 5a092e2..da1255b 120000..100644
--- a/doc/images/b2qt-demo-graphicaleffects.jpg
+++ b/doc/images/b2qt-demo-graphicaleffects.jpg
Binary files differ
diff --git a/doc/images/b2qt-demo-mediaplayer.jpg b/doc/images/b2qt-demo-mediaplayer.jpg
index cce5a00..d61efbc 120000..100644
--- a/doc/images/b2qt-demo-mediaplayer.jpg
+++ b/doc/images/b2qt-demo-mediaplayer.jpg
Binary files differ
diff --git a/doc/images/b2qt-demo-qtquickcontrols2.jpg b/doc/images/b2qt-demo-qtquickcontrols2.jpg
new file mode 100644
index 0000000..89a4fcb
--- /dev/null
+++ b/doc/images/b2qt-demo-qtquickcontrols2.jpg
Binary files differ
diff --git a/doc/images/b2qt-demo-qtwebbrowser.jpg b/doc/images/b2qt-demo-qtwebbrowser.jpg
index 13dea2f..c63de25 120000..100644
--- a/doc/images/b2qt-demo-qtwebbrowser.jpg
+++ b/doc/images/b2qt-demo-qtwebbrowser.jpg
Binary files differ
diff --git a/tradeshow/iot-sensortag/SensorTagDemo.pro b/tradeshow/iot-sensortag/SensorTagDemo.pro
index bd79351..6be190e 100644
--- a/tradeshow/iot-sensortag/SensorTagDemo.pro
+++ b/tradeshow/iot-sensortag/SensorTagDemo.pro
@@ -1,33 +1,41 @@
TEMPLATE = app
-QT += 3dcore 3drender 3dinput 3dquick 3dlogic core gui qml quick 3dquickextras widgets
-QT += bluetooth network charts
+QT += \
+ bluetooth \
+ core \
+ charts \
+ gui \
+ network \
+ qml \
+ quick \
+ widgets
+
CONFIG += c++11
DEFINES += QT_NO_FOREACH
+# Specify UI layout to use: UI_SMALL or UI_WATCH
+DEFINES += UI_WATCH
+
# To overcome the bug QTBUG-58648, uncomment this define
# Needed at least for RPi3 and iMX
#CONFIG += DEPLOY_TO_FS
-# Uncomment DEVICE_TYPE and assign either UI_SMALL, UI_MEDIUM, UI_LARGE
-# to force using that UI form factor. Otherwise
-# the form factor is determined based on the platform
-DEVICE_TYPE = UI_SMALL
-
-# If DEVICE_TYPE is not set manually, try to determine
-# the correct device type by the used operating system
-win32|linux:!android:!qnx {
+win32|linux|android:!qnx {
CONFIG += BLUETOOTH_HOST
- isEmpty(DEVICE_TYPE) { DEVICE_TYPE = UI_SMALL }
-} else:android {
- isEmpty(DEVICE_TYPE) { DEVICE_TYPE = UI_MEDIUM }
- ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources
- QMAKE_CXX_FLAGS -= -DQT_OPENGL_FORCE_SHADER_DEFINES
-} else:ios {
- isEmpty(DEVICE_TYPE) { DEVICE_TYPE = UI_MEDIUM }
+} else {
+ message(Unsupported target platform)
}
-win32 {
+# For using MQTT upload enable this config.
+# This enables both, host and client mode
+# CONFIG += UPDATE_TO_MQTT_BROKER
+
+# For using Azure cloud connectivity enable
+# this config. This enabled both, host and
+# client mode
+# CONFIG += UPDATE_TO_AZURE
+
+win32:!contains(CONFIG, UPDATE_TO_MQTT_BROKER) {
WASTORAGE_PATH = $$(WASTORAGE_LOCATION)
isEmpty(WASTORAGE_PATH): message("Location for Azure Storage libs unknown. Please specify WASTORAGE_LOCATION")
CPPRESTSDK_PATH = $$(CPPRESTSDK_LOCATION)
@@ -61,11 +69,6 @@ HEADERS += \
mockdataproviderpool.h
BLUETOOTH_HOST {
- win32 {
- !isEmpty(WASTORAGE_PATH):!isEmpty(CPPRESTSDK_LOCATION): CONFIG += UPDATE_TO_AZURE
- } else {
- CONFIG += UPDATE_TO_AZURE
- }
DEFINES += RUNS_AS_HOST
SOURCES += \
@@ -83,6 +86,21 @@ BLUETOOTH_HOST {
bluetoothdevice.h
}
+UPDATE_TO_MQTT_BROKER {
+ CONFIG -= UPDATE_TO_AZURE
+
+ !qtHaveModule(mqtt): error("Could not find MQTT module for Qt version")
+ QT += mqtt
+ DEFINES += MQTT_UPLOAD
+
+ SOURCES += mqttupdate.cpp \
+ mqttdataproviderpool.cpp \
+ mqttdataprovider.cpp
+ HEADERS += mqttupdate.h \
+ mqttdataproviderpool.h \
+ mqttdataprovider.h
+}
+
UPDATE_TO_AZURE {
SOURCES += cloudupdate.cpp
HEADERS += cloudupdate.h
@@ -100,29 +118,17 @@ UPDATE_TO_AZURE {
RESOURCES += base.qrc
-equals(DEVICE_TYPE, "UI_SMALL") {
- DEFINES += UI_SMALL
+android: ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources
+
+contains(DEFINES, UI_SMALL) {
!DEPLOY_TO_FS: RESOURCES += uismall.qrc
uiVariant.files = resources/small
- uiVariant.path = /opt/$${TARGET}/resources
- message("Resource file for SMALL display picked")
-}
-
-equals(DEVICE_TYPE, "UI_MEDIUM") {
- DEFINES += UI_MEDIUM
- !DEPLOY_TO_FS: RESOURCES += uimedium.qrc
- uiVariant.files = resources/medium
- uiVariant.path = /opt/$${TARGET}/resources
- message("Resource file for MEDIUM display picked")
-}
+} else:contains(DEFINES, UI_WATCH) {
+ !DEPLOY_TO_FS: RESOURCES += uiwatch.qrc
+ uiVariant.files = resources/watch
+} else: error("No layout specified")
-equals(DEVICE_TYPE, "UI_LARGE") {
- DEFINES += UI_LARGE
- !DEPLOY_TO_FS: RESOURCES += uilarge.qrc
- uiVariant.files = resources/large
- uiVariant.path = /opt/$${TARGET}/resources
- message("Resource file for LARGE display picked")
-}
+uiVariant.path = /opt/$${TARGET}/resources
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
diff --git a/tradeshow/iot-sensortag/bluetoothdataprovider.cpp b/tradeshow/iot-sensortag/bluetoothdataprovider.cpp
index 06e9fd5..3c81d92 100644
--- a/tradeshow/iot-sensortag/bluetoothdataprovider.cpp
+++ b/tradeshow/iot-sensortag/bluetoothdataprovider.cpp
@@ -55,9 +55,10 @@ Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos)
#define SAMPLE_COUNT_FOR_ZERO_ALTITUDE 10
-BluetoothDataProvider::BluetoothDataProvider(QString id, QObject *parent)
- : SensorTagDataProvider(id, parent)
- , m_btDevice(Q_NULLPTR)
+BluetoothDataProvider::BluetoothDataProvider(const QBluetoothDeviceInfo &id, QObject *parent)
+ : SensorTagDataProvider(id.address().toString(), parent)
+ , m_info(id)
+ , m_btDevice(nullptr)
, m_smaSamples(0)
, m_zeroAltitudeSamples(0)
, gyroscopeX_calibration(0)
@@ -76,42 +77,52 @@ BluetoothDataProvider::~BluetoothDataProvider()
bool BluetoothDataProvider::startDataFetching()
{
- qCDebug(boot2QtDemos) << Q_FUNC_INFO;
- if (m_btDevice) {
- connect(m_btDevice, &BluetoothDevice::statusUpdated, this, [=](const QString& statusMsg) {
- qCDebug(boot2QtDemos) << id() << "----------" << statusMsg;
- });
- connect(m_btDevice, &BluetoothDevice::stateChanged,
- this, &BluetoothDataProvider::updateState);
- connect(m_btDevice, &BluetoothDevice::temperatureChanged,
- this, &BluetoothDataProvider::temperatureReceived);
- connect(m_btDevice, &BluetoothDevice::barometerChanged,
- this, &BluetoothDataProvider::barometerReceived);
- connect(m_btDevice, &BluetoothDevice::humidityChanged,
- this, &BluetoothDataProvider::humidityReceived);
- connect(m_btDevice, &BluetoothDevice::lightIntensityChanged,
- this, &BluetoothDataProvider::lightIntensityReceived);
- connect(m_btDevice, &BluetoothDevice::motionChanged,
- this, &BluetoothDataProvider::motionReceived);
- startServiceScan();
- }
+ qCDebug(boot2QtDemos) << Q_FUNC_INFO << " :" << m_btDevice;
+
+ m_btDevice = new BluetoothDevice(m_info);
+
+ connect(m_btDevice, &BluetoothDevice::statusUpdated, this, [=](const QString& statusMsg) {
+ qCDebug(boot2QtDemos) << id() << "----------" << statusMsg;
+ });
+ connect(m_btDevice, &BluetoothDevice::stateChanged,
+ this, &BluetoothDataProvider::updateState);
+ connect(m_btDevice, &BluetoothDevice::temperatureChanged,
+ this, &BluetoothDataProvider::temperatureReceived);
+ connect(m_btDevice, &BluetoothDevice::barometerChanged,
+ this, &BluetoothDataProvider::barometerReceived);
+ connect(m_btDevice, &BluetoothDevice::humidityChanged,
+ this, &BluetoothDataProvider::humidityReceived);
+ connect(m_btDevice, &BluetoothDevice::lightIntensityChanged,
+ this, &BluetoothDataProvider::lightIntensityReceived);
+ connect(m_btDevice, &BluetoothDevice::motionChanged,
+ this, &BluetoothDataProvider::motionReceived);
+ startServiceScan();
+
return true;
}
void BluetoothDataProvider::endDataFetching()
{
+ // BluetoothDevice is not capable of restarting
+ qCDebug(boot2QtDemos) << Q_FUNC_INFO << " :" << m_btDevice;
+ m_btDevice->disconnectFromDevice();
+ setState(Scanning);
+
+ m_btDevice->deleteLater();
+ m_btDevice = nullptr;
}
void BluetoothDataProvider::startServiceScan()
{
- qCDebug(boot2QtDemos)<<Q_FUNC_INFO;
+ qCDebug(boot2QtDemos) << Q_FUNC_INFO << m_btDevice;
if (m_btDevice) {
setState(Scanning);
m_btDevice->scanServices();
}
}
-void BluetoothDataProvider::temperatureReceived(double newAmbientTemperature, double newObjectTemperature)
+void BluetoothDataProvider::temperatureReceived(double newAmbientTemperature,
+ double newObjectTemperature)
{
/* NOTE: We emit the signals even if value is unchanged.
* Otherwise the scrolling graphs in UI will not scroll.
@@ -129,17 +140,18 @@ void BluetoothDataProvider::barometerReceived(double temperature, double baromet
*/
barometerCelsiusTemperature = temperature;
emit barometerCelsiusTemperatureChanged();
- barometerTemperatureAverage = (temperature + m_smaSamples * barometerTemperatureAverage) / (m_smaSamples + 1);
+ barometerTemperatureAverage = (temperature + m_smaSamples * barometerTemperatureAverage)
+ / (m_smaSamples + 1);
m_smaSamples++;
emit barometerCelsiusTemperatureAverageChanged();
- // Use a limited number of samples. It will eventually give wrong avg values, but this is just a demo...
+ // Use a limited number of samples. It will eventually give wrong avg values,
+ // but this is just a demo...
if (m_smaSamples > 10000)
m_smaSamples = 0;
barometerHPa = barometer;
emit barometer_hPaChanged();
recalibrateZeroAltitude();
-
calculateZeroAltitude();
}
@@ -167,7 +179,7 @@ float BluetoothDataProvider::countRotationDegrees(double degreesPerSecond, quint
return ((float)degreesPerSecond) * seconds;
}
-void BluetoothDataProvider::motionReceived(MotionSensorData &data)
+void BluetoothDataProvider::motionReceived(const MotionSensorData &data)
{
/* NOTE: We emit the signals even if value is unchanged.
* Otherwise the scrolling graphs in UI will not scroll.
@@ -222,9 +234,11 @@ QString BluetoothDataProvider::versionString() const
void BluetoothDataProvider::updateState()
{
+ if (!m_btDevice)
+ return;
+
switch (m_btDevice->state()) {
case BluetoothDevice::Disconnected:
- unbindDevice();
break;
case BluetoothDevice::Scanning:
setState(Scanning);
@@ -269,13 +283,6 @@ void BluetoothDataProvider::recalibrateZeroAltitude()
}
}
-void BluetoothDataProvider::bindToDevice(BluetoothDevice *device)
-{
- if (m_btDevice)
- delete m_btDevice;
- m_btDevice = device;
-}
-
void BluetoothDataProvider::unbindDevice()
{
if (m_btDevice) {
diff --git a/tradeshow/iot-sensortag/bluetoothdataprovider.h b/tradeshow/iot-sensortag/bluetoothdataprovider.h
index 5f39b4f..5c28725 100644
--- a/tradeshow/iot-sensortag/bluetoothdataprovider.h
+++ b/tradeshow/iot-sensortag/bluetoothdataprovider.h
@@ -55,12 +55,13 @@
#include <QQmlEngine>
#include <QJSEngine>
#include <QTimer>
+#include <QBluetoothDeviceInfo>
class BluetoothDataProvider : public SensorTagDataProvider
{
Q_OBJECT
public:
- BluetoothDataProvider(QString id, QObject* parent = 0);
+ BluetoothDataProvider(const QBluetoothDeviceInfo &id, QObject *parent = 0);
virtual ~BluetoothDataProvider();
@@ -69,9 +70,8 @@ public:
QString sensorType() const;
QString versionString() const;
- void bindToDevice(BluetoothDevice *device);
void unbindDevice();
- BluetoothDevice* device();
+ BluetoothDevice *device();
public slots:
void startServiceScan();
@@ -80,7 +80,7 @@ public slots:
void barometerReceived(double temperature, double barometer);
void humidityReceived(double humidity);
void lightIntensityReceived(double lightIntensity);
- void motionReceived(MotionSensorData &data);
+ void motionReceived(const MotionSensorData &data);
protected:
void reset() override;
@@ -89,6 +89,7 @@ protected:
private:
void updateState();
float countRotationDegrees(double degreesPerSecond, quint64 milliseconds);
+ QBluetoothDeviceInfo m_info;
BluetoothDevice *m_btDevice;
int m_smaSamples;
int m_zeroAltitudeSamples;
diff --git a/tradeshow/iot-sensortag/bluetoothdevice.cpp b/tradeshow/iot-sensortag/bluetoothdevice.cpp
index 90681ed..2ede116 100644
--- a/tradeshow/iot-sensortag/bluetoothdevice.cpp
+++ b/tradeshow/iot-sensortag/bluetoothdevice.cpp
@@ -139,22 +139,20 @@ void BluetoothDevice::scanServices()
statusUpdated("(Connecting to device...)");
// Connecting signals and slots for connecting to LE services.
m_controller = new QLowEnergyController(m_deviceInfo);
- connect(m_controller, SIGNAL(connected()),
- this, SLOT(deviceConnected()));
- connect(m_controller, SIGNAL(error(QLowEnergyController::Error)),
- this, SLOT(errorReceived(QLowEnergyController::Error)));
- connect(m_controller, SIGNAL(disconnected()),
- this, SLOT(deviceDisconnected()));
- connect(m_controller, SIGNAL(serviceDiscovered(QBluetoothUuid)),
- this, SLOT(addLowEnergyService(QBluetoothUuid)));
- connect(m_controller, SIGNAL(discoveryFinished()),
- this, SLOT(serviceScanDone()));
+ connect(m_controller, &QLowEnergyController::connected,
+ this, &BluetoothDevice::deviceConnected);
+ connect(m_controller, QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
+ this, &BluetoothDevice::errorReceived);
+ connect(m_controller, &QLowEnergyController::disconnected,
+ this, &BluetoothDevice::deviceDisconnected);
+ connect(m_controller, &QLowEnergyController::serviceDiscovered,
+ this, &BluetoothDevice::addLowEnergyService);
+ connect(m_controller, &QLowEnergyController::discoveryFinished,
+ this, &BluetoothDevice::serviceScanDone);
m_controller->setRemoteAddressType(QLowEnergyController::PublicAddress);
- m_controller->connectToDevice();
- } else {
- deviceConnected();
}
+ m_controller->connectToDevice();
}
void BluetoothDevice::addLowEnergyService(const QBluetoothUuid &serviceUuid)
@@ -166,8 +164,10 @@ void BluetoothDevice::addLowEnergyService(const QBluetoothUuid &serviceUuid)
qWarning() << "Could not create infrared temperature service object.";
return;
}
- connect(m_irTemperatureService, &QLowEnergyService::stateChanged, this, &BluetoothDevice::temperatureDetailsDiscovered);
- connect(m_irTemperatureService, &QLowEnergyService::characteristicChanged, this, &BluetoothDevice::updateTemperature);
+ connect(m_irTemperatureService, &QLowEnergyService::stateChanged,
+ this, &BluetoothDevice::temperatureDetailsDiscovered);
+ connect(m_irTemperatureService, &QLowEnergyService::characteristicChanged,
+ this, &BluetoothDevice::updateTemperature);
m_irTemperatureService->discoverDetails();
} else if (serviceUuid == QBluetoothUuid(QLatin1String(BAROMETER_SERVICE_UUID))) {
qCDebug(boot2QtDemos) << "Found barometer service.";
@@ -176,8 +176,10 @@ void BluetoothDevice::addLowEnergyService(const QBluetoothUuid &serviceUuid)
qWarning() << "Could not create barometer service object.";
return;
}
- connect(m_baroService, &QLowEnergyService::stateChanged, this, &BluetoothDevice::barometerDetailsDiscovered);
- connect(m_baroService, &QLowEnergyService::characteristicChanged, this, &BluetoothDevice::updatePressure);
+ connect(m_baroService, &QLowEnergyService::stateChanged,
+ this, &BluetoothDevice::barometerDetailsDiscovered);
+ connect(m_baroService, &QLowEnergyService::characteristicChanged,
+ this, &BluetoothDevice::updatePressure);
m_baroService->discoverDetails();
} else if (serviceUuid == QBluetoothUuid(QLatin1String(HUMIDITYSENSOR_SERVICE_UUID))) {
qCDebug(boot2QtDemos) << "Found humidity service.";
@@ -186,8 +188,10 @@ void BluetoothDevice::addLowEnergyService(const QBluetoothUuid &serviceUuid)
qWarning() << "Could not create humidity service object.";
return;
}
- connect(m_humidityService, &QLowEnergyService::stateChanged, this, &BluetoothDevice::humidityDetailsDiscovered);
- connect(m_humidityService, &QLowEnergyService::characteristicChanged, this, &BluetoothDevice::updateHumidity);
+ connect(m_humidityService, &QLowEnergyService::stateChanged,
+ this, &BluetoothDevice::humidityDetailsDiscovered);
+ connect(m_humidityService, &QLowEnergyService::characteristicChanged,
+ this, &BluetoothDevice::updateHumidity);
m_humidityService->discoverDetails();
} else if (serviceUuid == QBluetoothUuid(QLatin1String(LIGHTSENSOR_SERVICE_UUID))) {
qCDebug(boot2QtDemos) << "Found light service.";
@@ -196,8 +200,10 @@ void BluetoothDevice::addLowEnergyService(const QBluetoothUuid &serviceUuid)
qWarning() << "Could not create light service object.";
return;
}
- connect(m_lightService, &QLowEnergyService::stateChanged, this, &BluetoothDevice::lightIntensityDetailsDiscovered);
- connect(m_lightService, &QLowEnergyService::characteristicChanged, this, &BluetoothDevice::updateLight);
+ connect(m_lightService, &QLowEnergyService::stateChanged,
+ this, &BluetoothDevice::lightIntensityDetailsDiscovered);
+ connect(m_lightService, &QLowEnergyService::characteristicChanged,
+ this, &BluetoothDevice::updateLight);
m_lightService->discoverDetails();
} else if (serviceUuid == QBluetoothUuid(QLatin1String(MOTIONSENSOR_SERVICE_UUID))) {
qCDebug(boot2QtDemos) << "Found motion service.";
@@ -206,8 +212,10 @@ void BluetoothDevice::addLowEnergyService(const QBluetoothUuid &serviceUuid)
qWarning() << "Could not create motion service object.";
return;
}
- connect(m_motionService, &QLowEnergyService::stateChanged, this, &BluetoothDevice::motionDetailsDiscovered);
- connect(m_motionService, &QLowEnergyService::characteristicChanged, this, &BluetoothDevice::updateMotionValue);
+ connect(m_motionService, &QLowEnergyService::stateChanged,
+ this, &BluetoothDevice::motionDetailsDiscovered);
+ connect(m_motionService, &QLowEnergyService::characteristicChanged,
+ this, &BluetoothDevice::updateMotionValue);
m_motionService->discoverDetails();
} else {
qCDebug(boot2QtDemos) << "Found unhandled service with id" << serviceUuid << ".";
@@ -243,7 +251,7 @@ void BluetoothDevice::temperatureDetailsDiscovered(QLowEnergyService::ServiceSta
qCDebug(boot2QtDemos) << "Wrote Characteristic - temperature";
});
- connect(m_irTemperatureService, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
+ connect(m_irTemperatureService, QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
[=](QLowEnergyService::ServiceError newError) {
qCDebug(boot2QtDemos) << "error while writing - temperature:" << newError;
});
@@ -282,7 +290,7 @@ void BluetoothDevice::barometerDetailsDiscovered(QLowEnergyService::ServiceState
qCDebug(boot2QtDemos) << "Wrote Characteristic - barometer";
});
- connect(m_baroService, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
+ connect(m_baroService, QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
[=](QLowEnergyService::ServiceError newError) {
qCDebug(boot2QtDemos) << "error while writing - barometer:" << newError;
});
@@ -322,7 +330,7 @@ void BluetoothDevice::humidityDetailsDiscovered(QLowEnergyService::ServiceState
qCDebug(boot2QtDemos) << "Wrote Characteristic - humidity";
});
- connect(m_humidityService, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
+ connect(m_humidityService, QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
[=](QLowEnergyService::ServiceError newError) {
qCDebug(boot2QtDemos) << "error while writing - humidity:" << newError;
});
@@ -362,7 +370,7 @@ void BluetoothDevice::lightIntensityDetailsDiscovered(QLowEnergyService::Service
qCDebug(boot2QtDemos) << "Wrote Characteristic - light intensity";
});
- connect(m_lightService, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
+ connect(m_lightService, QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
[=](QLowEnergyService::ServiceError newError) {
qCDebug(boot2QtDemos) << "error while writing - light intensity:" << newError;
});
@@ -405,7 +413,7 @@ void BluetoothDevice::motionDetailsDiscovered(QLowEnergyService::ServiceState ne
qCDebug(boot2QtDemos) << "Wrote Characteristic - gyro";
});
- connect(m_motionService, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
+ connect(m_motionService, QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
[=](QLowEnergyService::ServiceError newError) {
qCDebug(boot2QtDemos) << "error while writing - gyro:" << newError;
});
diff --git a/tradeshow/iot-sensortag/bluetoothdevice.h b/tradeshow/iot-sensortag/bluetoothdevice.h
index ed74866..5f114ea 100644
--- a/tradeshow/iot-sensortag/bluetoothdevice.h
+++ b/tradeshow/iot-sensortag/bluetoothdevice.h
@@ -63,28 +63,16 @@
class MotionSensorData
{
public:
- double gyroScope_x;
- double gyroScope_y;
- double gyroScope_z;
- double accelometer_x;
- double accelometer_y;
- double accelometer_z;
- double magnetometer_x;
- double magnetometer_y;
- double magnetometer_z;
- quint64 msSincePreviousData;
- MotionSensorData() {
- gyroScope_x = 0;
- gyroScope_y = 0;
- gyroScope_z = 0;
- accelometer_x = 0;
- accelometer_y = 0;
- accelometer_z = 0;
- magnetometer_x = 0;
- magnetometer_y = 0;
- magnetometer_z = 0;
- msSincePreviousData = 0;
- }
+ double gyroScope_x{};
+ double gyroScope_y{};
+ double gyroScope_z{};
+ double accelometer_x{};
+ double accelometer_y{};
+ double accelometer_z{};
+ double magnetometer_x{};
+ double magnetometer_y{};
+ double magnetometer_z{};
+ quint64 msSincePreviousData{};
};
typedef enum CharacteristicType {
@@ -105,7 +93,12 @@ class BluetoothDevice: public QObject
Q_PROPERTY(DeviceState state READ state NOTIFY stateChanged)
public:
- enum DeviceState {Disconnected = 0, Scanning, Connected, Error};
+ enum DeviceState {
+ Disconnected = 0,
+ Scanning,
+ Connected,
+ Error
+ };
Q_ENUM(DeviceState)
BluetoothDevice();
@@ -124,7 +117,7 @@ signals:
void barometerChanged(double temperature, double barometer);
void humidityChanged(double humidity);
void lightIntensityChanged(double intensity);
- void motionChanged(MotionSensorData& data);
+ void motionChanged(MotionSensorData data);
void statusUpdated(QString statusMsg);
public slots:
@@ -170,11 +163,11 @@ private:
void updateServiceDetails();
QLowEnergyController *m_controller;
- QLowEnergyService* m_irTemperatureService;
- QLowEnergyService* m_baroService;
- QLowEnergyService* m_humidityService;
- QLowEnergyService* m_lightService;
- QLowEnergyService* m_motionService;
+ QLowEnergyService *m_irTemperatureService;
+ QLowEnergyService *m_baroService;
+ QLowEnergyService *m_humidityService;
+ QLowEnergyService *m_lightService;
+ QLowEnergyService *m_motionService;
DeviceState m_deviceState;
bool m_temperatureMeasurementStarted;
bool m_barometerMeasurementStarted;
@@ -182,11 +175,8 @@ private:
bool m_lightIntensityMeasurementStarted;
bool m_motionMeasurementStarted;
quint64 m_lastMilliseconds;
-
QBluetoothDeviceInfo m_deviceInfo;
-
SensorTagDataProvider *m_dataProvider;
-
QTimer *m_serviceDetailsTimer;
};
diff --git a/tradeshow/iot-sensortag/clouddataprovider.cpp b/tradeshow/iot-sensortag/clouddataprovider.cpp
index 0c2bd0f..aa5a26c 100644
--- a/tradeshow/iot-sensortag/clouddataprovider.cpp
+++ b/tradeshow/iot-sensortag/clouddataprovider.cpp
@@ -55,7 +55,7 @@
#define MAJOR_VERSION_NUMBER 1
#define MINOR_VERSION_NUMBER 1
-#define CLOUD_DATA_POLL_INTERVAL_MS 1000 /* 1 second update interval */
+#define CLOUD_DATA_POLL_INTERVAL_MS 1000 /* 1 second update interval */
#ifndef QT_NO_SSL
static QString dataFetchUrl = "https://ottoryynanenqt.blob.core.windows.net/btsensortagreadings/sensorTagReadings.txt";
#else
@@ -64,9 +64,9 @@ static QString dataFetchUrl = "http://ottoryynanenqt.blob.core.windows.net/btsen
Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos)
-CloudDataProvider::CloudDataProvider(QString id, QObject* parent)
+CloudDataProvider::CloudDataProvider(QString id, QObject *parent)
: SensorTagDataProvider(id, parent)
- , reply(Q_NULLPTR)
+ , reply(nullptr)
{
intervalRotation = CLOUD_DATA_POLL_INTERVAL_MS;
connect(&qnam, &QNetworkAccessManager::authenticationRequired,
@@ -79,8 +79,9 @@ CloudDataProvider::CloudDataProvider(QString id, QObject* parent)
bool CloudDataProvider::startDataFetching()
{
+ setState(Connected);
pollTimer = new QTimer(this);
- connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollTimerExpired()));
+ connect(pollTimer, &QTimer::timeout, this, &CloudDataProvider::pollTimerExpired);
pollTimer->start(CLOUD_DATA_POLL_INTERVAL_MS);
return true;
}
@@ -105,7 +106,8 @@ void CloudDataProvider::parseReceivedText()
if ((MAJOR_VERSION_NUMBER < dataMajorVersion) || // Major version not supported OR
((MAJOR_VERSION_NUMBER == dataMajorVersion) && // Major version OK but
(MINOR_VERSION_NUMBER < dataMinorVersion))) { // Minor version not supported
- qWarning() << Q_FUNC_INFO << "Version" << dataList[3] << "not supported by version" << QString::number(MAJOR_VERSION_NUMBER)+"."+QString::number(MINOR_VERSION_NUMBER);
+ qWarning() << Q_FUNC_INFO << "Version" << dataList[3] << "not supported by version"
+ << QString::number(MAJOR_VERSION_NUMBER)+"."+QString::number(MINOR_VERSION_NUMBER);
return;
}
// Header OK, parse data.
@@ -113,10 +115,10 @@ void CloudDataProvider::parseReceivedText()
bool accelometerReadingGot = false;
bool magnetometerReadingGot = false;
bool rotationReadingsGot = false;
- for (int stringIndex = 4 ; stringIndex < (dataList.length()-1) ; stringIndex+=2) {
+ for (int stringIndex = 4; stringIndex < (dataList.length() - 1); stringIndex += 2) {
const QString headerText(dataList[stringIndex]);
- const double doubleValue = QString(dataList[stringIndex+1]).toDouble();
- const float floatValue = QString(dataList[stringIndex+1]).toFloat();
+ const double doubleValue = QString(dataList[stringIndex + 1]).toDouble();
+ const float floatValue = QString(dataList[stringIndex + 1]).toFloat();
/* NOTE: We emit the signals even if value is unchanged.
* Otherwise the scrolling graphs in UI will not scroll.
*/
@@ -216,10 +218,8 @@ void CloudDataProvider::httpFinished()
}
parseReceivedText();
- const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
-
reply->deleteLater();
- reply = Q_NULLPTR;
+ reply = nullptr;
}
void CloudDataProvider::httpReadyRead()
@@ -232,7 +232,7 @@ void CloudDataProvider::httpReadyRead()
}
#ifndef QT_NO_SSL
-void CloudDataProvider::sslErrors(QNetworkReply*, const QList<QSslError> &errors)
+void CloudDataProvider::sslErrors(QNetworkReply *, const QList<QSslError> &errors)
{
QString errorString;
for (const QSslError &error : errors) {
@@ -246,7 +246,7 @@ void CloudDataProvider::sslErrors(QNetworkReply*, const QList<QSslError> &errors
}
#endif
-void CloudDataProvider::slotAuthenticationRequired(QNetworkReply*, QAuthenticator *authenticator)
+void CloudDataProvider::slotAuthenticationRequired(QNetworkReply *, QAuthenticator *authenticator)
{
Q_UNUSED(authenticator);
}
@@ -258,5 +258,5 @@ QString CloudDataProvider::sensorType() const
QString CloudDataProvider::versionString() const
{
- return QString::number(MAJOR_VERSION_NUMBER)+"."+QString::number(MINOR_VERSION_NUMBER);
+ return QString::number(MAJOR_VERSION_NUMBER) + "." + QString::number(MINOR_VERSION_NUMBER);
}
diff --git a/tradeshow/iot-sensortag/clouddataproviderpool.cpp b/tradeshow/iot-sensortag/clouddataproviderpool.cpp
index 7265059..5b69803 100644
--- a/tradeshow/iot-sensortag/clouddataproviderpool.cpp
+++ b/tradeshow/iot-sensortag/clouddataproviderpool.cpp
@@ -50,7 +50,7 @@
#include "clouddataproviderpool.h"
#include "clouddataprovider.h"
-CloudDataProviderPool::CloudDataProviderPool(QObject* parent)
+CloudDataProviderPool::CloudDataProviderPool(QObject *parent)
: DataProviderPool(parent)
{
m_poolName = "Cloud";
@@ -60,9 +60,8 @@ void CloudDataProviderPool::startScanning()
{
qDeleteAll(m_dataProviders);
m_dataProviders.clear();
-
m_dataProviders.push_back(new CloudDataProvider("CLOUD_PROVIDER", this));
- m_dataProviders.at(0)->startDataFetching();
+
emit providerConnected("MS_AZURE_CLOUD");
emit providersUpdated();
emit dataProvidersChanged();
diff --git a/tradeshow/iot-sensortag/clouddataproviderpool.h b/tradeshow/iot-sensortag/clouddataproviderpool.h
index 5a357c0..3a13a19 100644
--- a/tradeshow/iot-sensortag/clouddataproviderpool.h
+++ b/tradeshow/iot-sensortag/clouddataproviderpool.h
@@ -57,7 +57,7 @@ class CloudDataProvider;
class CloudDataProviderPool : public DataProviderPool
{
public:
- explicit CloudDataProviderPool(QObject* parent = 0);
+ explicit CloudDataProviderPool(QObject *parent = 0);
void startScanning() override;
};
diff --git a/tradeshow/iot-sensortag/cloudupdate.cpp b/tradeshow/iot-sensortag/cloudupdate.cpp
index 122fd9e..b43c097 100644
--- a/tradeshow/iot-sensortag/cloudupdate.cpp
+++ b/tradeshow/iot-sensortag/cloudupdate.cpp
@@ -47,11 +47,11 @@
** $QT_END_LICENSE$
**
****************************************************************************/
+
#include "cloudupdate.h"
#include "cloudservice.h"
#include "dataproviderpool.h"
#include "sensortagdataprovider.h"
-
#include "demodataproviderpool.h"
#include "was/storage_account.h"
@@ -72,11 +72,10 @@ CloudUpdate::CloudUpdate(QObject *parent)
void CloudUpdate::setDataProviderPool(DataProviderPool *provider)
{
m_providerPool = provider;
- connect(m_providerPool, &DataProviderPool::providerForCloudChanged, this, [=]() {
- m_provider = m_providerPool->providerForCloud();
- if (!m_provider) {
+ connect(m_providerPool, &DataProviderPool::currentProviderChanged, this, [=]() {
+ m_provider = m_providerPool->currentProvider();
+ if (!m_provider)
stop();
- }
});
}
@@ -126,7 +125,7 @@ void CloudUpdate::writeToCloud()
#ifndef Q_OS_WIN
const utility::string_t storage_connection_string(U(accStr.data()));
#else
- const utility::string_t storage_connection_string(reinterpret_cast<const wchar_t*>(QString(accStr).utf16()));
+ const utility::string_t storage_connection_string(reinterpret_cast<const wchar_t *>(QString(accStr).utf16()));
#endif
azure::storage::cloud_blob_container container;
@@ -154,7 +153,7 @@ void CloudUpdate::writeToCloud()
#ifndef Q_OS_WIN
blob.upload_text(U(sensorData.toLocal8Bit().data()));
#else
- blob.upload_text(reinterpret_cast<const wchar_t*>(sensorData.utf16()));
+ blob.upload_text(reinterpret_cast<const wchar_t *>(sensorData.utf16()));
#endif
}
@@ -181,5 +180,6 @@ QString CloudUpdate::buildString() const
exportString += QString("RotX:\n%1\n").arg(m_provider->getRotationX(), 0, 'f', ROUNDING_DECIMALS);
exportString += QString("RotY:\n%1\n").arg(m_provider->getRotationY(), 0, 'f', ROUNDING_DECIMALS);
exportString += QString("RotZ:\n%1").arg(m_provider->getRotationZ(), 0, 'f', ROUNDING_DECIMALS);
+
return exportString;
}
diff --git a/tradeshow/iot-sensortag/cloudupdate.h b/tradeshow/iot-sensortag/cloudupdate.h
index 9fa81cf..f6026ce 100644
--- a/tradeshow/iot-sensortag/cloudupdate.h
+++ b/tradeshow/iot-sensortag/cloudupdate.h
@@ -68,7 +68,7 @@ public:
void stop();
protected:
- void timerEvent(QTimerEvent* event);
+ void timerEvent(QTimerEvent *event);
virtual void writeToCloud();
diff --git a/tradeshow/iot-sensortag/dataproviderpool.cpp b/tradeshow/iot-sensortag/dataproviderpool.cpp
index 77c4514..66d9480 100644
--- a/tradeshow/iot-sensortag/dataproviderpool.cpp
+++ b/tradeshow/iot-sensortag/dataproviderpool.cpp
@@ -51,12 +51,16 @@
DataProviderPool::DataProviderPool(QObject *parent)
: QObject(parent)
+ , m_currentProvider(nullptr)
+ , m_currentProviderIndex(-1)
{
}
DataProviderPool::DataProviderPool(QString poolName, QObject *parent)
: QObject(parent)
, m_poolName(poolName)
+ , m_currentProvider(nullptr)
+ , m_currentProviderIndex(-1)
{
}
@@ -70,21 +74,7 @@ void DataProviderPool::stopScanning()
emit scanFinished();
}
-SensorTagDataProvider *DataProviderPool::getProvider(SensorTagDataProvider::TagType type) const
-{
- auto it = m_dataProviders.constBegin();
- SensorTagDataProvider* p = 0;
- while (it != m_dataProviders.end()) {
- if ((*it)->tagType() & type) {
- p = *it;
- break;
- }
- it++;
- }
- return p;
-}
-
-void DataProviderPool::disconnectProvider(QString id)
+void DataProviderPool::disconnectProvider(const QString &id)
{
Q_UNUSED(id)
}
@@ -98,3 +88,24 @@ SensorTagDataProvider *DataProviderPool::providerForCloud() const
{
return 0;
}
+
+SensorTagDataProvider *DataProviderPool::currentProvider() const
+{
+ return m_currentProvider;
+}
+
+int DataProviderPool::currentProviderIndex() const
+{
+ return m_currentProviderIndex;
+}
+
+void DataProviderPool::setCurrentProviderIndex(int currentProviderIndex)
+{
+ if (m_currentProviderIndex == currentProviderIndex)
+ return;
+
+ m_currentProviderIndex = currentProviderIndex;
+ m_currentProvider = m_dataProviders.at(m_currentProviderIndex);
+ emit currentProviderIndexChanged(m_currentProviderIndex);
+ emit currentProviderChanged(m_currentProvider);
+}
diff --git a/tradeshow/iot-sensortag/dataproviderpool.h b/tradeshow/iot-sensortag/dataproviderpool.h
index 9c14504..e060758 100644
--- a/tradeshow/iot-sensortag/dataproviderpool.h
+++ b/tradeshow/iot-sensortag/dataproviderpool.h
@@ -58,18 +58,25 @@
class DataProviderPool : public QObject
{
Q_OBJECT
- Q_PROPERTY (QQmlListProperty<SensorTagDataProvider> dataProviders READ dataProviders NOTIFY dataProvidersChanged)
+ Q_PROPERTY(QQmlListProperty<SensorTagDataProvider> dataProviders READ dataProviders NOTIFY dataProvidersChanged)
+ Q_PROPERTY(SensorTagDataProvider* currentProvider READ currentProvider NOTIFY currentProviderChanged)
+ Q_PROPERTY(int currentProviderIndex READ currentProviderIndex WRITE setCurrentProviderIndex NOTIFY currentProviderIndexChanged)
Q_PROPERTY(QString name MEMBER m_poolName CONSTANT)
public:
Q_INVOKABLE virtual void startScanning();
Q_INVOKABLE virtual void stopScanning();
- Q_INVOKABLE virtual SensorTagDataProvider* getProvider(SensorTagDataProvider::TagType type) const;
- Q_INVOKABLE virtual void disconnectProvider(QString id);
+ Q_INVOKABLE virtual void disconnectProvider(const QString &id);
+ Q_INVOKABLE virtual SensorTagDataProvider *currentProvider() const;
+ Q_INVOKABLE virtual int currentProviderIndex() const;
QQmlListProperty<SensorTagDataProvider> dataProviders();
- virtual SensorTagDataProvider* providerForCloud() const;
+ virtual SensorTagDataProvider *providerForCloud() const;
+
+
+public slots:
+ void setCurrentProviderIndex(int currentProviderIndex);
signals:
void providerConnected(QString id);
@@ -80,14 +87,18 @@ signals:
void scanFinished();
void providerForCloudChanged();
void dataProvidersChanged();
+ void currentProviderChanged(SensorTagDataProvider* currentProvider);
+ void currentProviderIndexChanged(int currentProviderIndex);
protected:
explicit DataProviderPool(QObject *parent = 0);
- DataProviderPool(QString poolName, QObject* parent = 0);
+ DataProviderPool(QString poolName, QObject *parent = 0);
protected:
- QList<SensorTagDataProvider*> m_dataProviders;
+ QList<SensorTagDataProvider *> m_dataProviders;
QString m_poolName;
+ SensorTagDataProvider *m_currentProvider;
+ int m_currentProviderIndex;
};
#endif // DATAPROVIDERPOOL_H
diff --git a/tradeshow/iot-sensortag/demodataproviderpool.cpp b/tradeshow/iot-sensortag/demodataproviderpool.cpp
index f9a219d..4689a1c 100644
--- a/tradeshow/iot-sensortag/demodataproviderpool.cpp
+++ b/tradeshow/iot-sensortag/demodataproviderpool.cpp
@@ -83,7 +83,7 @@ QString DemoCloudProvider::versionString() const
double DemoCloudProvider::getRelativeHumidity() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Humidity)
return p->getRelativeHumidity();
}
@@ -92,7 +92,7 @@ double DemoCloudProvider::getRelativeHumidity() const
double DemoCloudProvider::getInfraredAmbientTemperature() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::AmbientTemperature)
return p->getInfraredAmbientTemperature();
}
@@ -101,7 +101,7 @@ double DemoCloudProvider::getInfraredAmbientTemperature() const
double DemoCloudProvider::getInfraredObjectTemperature() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::ObjectTemperature)
return p->getInfraredObjectTemperature();
}
@@ -110,7 +110,7 @@ double DemoCloudProvider::getInfraredObjectTemperature() const
double DemoCloudProvider::getLightIntensityLux() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Light)
return p->getLightIntensityLux();
}
@@ -119,7 +119,7 @@ double DemoCloudProvider::getLightIntensityLux() const
double DemoCloudProvider::getBarometerCelsiusTemperature() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::AirPressure)
return p->getBarometerCelsiusTemperature();
}
@@ -128,7 +128,7 @@ double DemoCloudProvider::getBarometerCelsiusTemperature() const
double DemoCloudProvider::getBarometerTemperatureAverage() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::AirPressure)
return p->getBarometerTemperatureAverage();
}
@@ -137,7 +137,7 @@ double DemoCloudProvider::getBarometerTemperatureAverage() const
double DemoCloudProvider::getBarometer_hPa() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::AirPressure)
return p->getBarometer_hPa();
}
@@ -146,7 +146,7 @@ double DemoCloudProvider::getBarometer_hPa() const
float DemoCloudProvider::getGyroscopeX_degPerSec() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Magnetometer)
return p->getGyroscopeX_degPerSec();
}
@@ -155,7 +155,7 @@ float DemoCloudProvider::getGyroscopeX_degPerSec() const
float DemoCloudProvider::getGyroscopeY_degPerSec() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Magnetometer)
return p->getGyroscopeY_degPerSec();
}
@@ -164,7 +164,7 @@ float DemoCloudProvider::getGyroscopeY_degPerSec() const
float DemoCloudProvider::getGyroscopeZ_degPerSec() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Magnetometer)
return p->getGyroscopeZ_degPerSec();
}
@@ -173,7 +173,7 @@ float DemoCloudProvider::getGyroscopeZ_degPerSec() const
float DemoCloudProvider::getAccelometer_xAxis() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Accelometer)
return p->getAccelometer_xAxis();
}
@@ -182,7 +182,7 @@ float DemoCloudProvider::getAccelometer_xAxis() const
float DemoCloudProvider::getAccelometer_yAxis() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Accelometer)
return p->getAccelometer_yAxis();
}
@@ -191,7 +191,7 @@ float DemoCloudProvider::getAccelometer_yAxis() const
float DemoCloudProvider::getAccelometer_zAxis() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Accelometer)
return p->getAccelometer_zAxis();
}
@@ -200,7 +200,7 @@ float DemoCloudProvider::getAccelometer_zAxis() const
float DemoCloudProvider::getMagnetometerMicroT_xAxis() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Magnetometer)
return p->getMagnetometerMicroT_xAxis();
}
@@ -209,7 +209,7 @@ float DemoCloudProvider::getMagnetometerMicroT_xAxis() const
float DemoCloudProvider::getMagnetometerMicroT_yAxis() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Magnetometer)
return p->getMagnetometerMicroT_yAxis();
}
@@ -218,7 +218,7 @@ float DemoCloudProvider::getMagnetometerMicroT_yAxis() const
float DemoCloudProvider::getMagnetometerMicroT_zAxis() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Magnetometer)
return p->getMagnetometerMicroT_zAxis();
}
@@ -227,7 +227,7 @@ float DemoCloudProvider::getMagnetometerMicroT_zAxis() const
float DemoCloudProvider::getRotationX() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Rotation)
return p->getRotationX();
}
@@ -236,7 +236,7 @@ float DemoCloudProvider::getRotationX() const
float DemoCloudProvider::getRotationY() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Rotation)
return p->getRotationY();
}
@@ -245,7 +245,7 @@ float DemoCloudProvider::getRotationY() const
float DemoCloudProvider::getRotationZ() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Rotation)
return p->getRotationZ();
}
@@ -254,7 +254,7 @@ float DemoCloudProvider::getRotationZ() const
float DemoCloudProvider::getAltitude() const
{
- for (SensorTagDataProvider *p : qAsConst(m_dataProviders)) {
+ for (SensorTagDataProvider *p: qAsConst(m_dataProviders)) {
if (p->tagType() & SensorTagDataProvider::Altitude)
return p->getAltitude();
}
@@ -272,74 +272,28 @@ DemoDataProviderPool::DemoDataProviderPool(QObject *parent)
void DemoDataProviderPool::startScanning()
{
- if (m_mockData) {
- qDeleteAll(m_dataProviders);
- m_dataProviders.clear();
-
- MockDataProvider* p = new MockDataProvider("MOCK_PROVIDER_1", this);
- p->setTagType(SensorTagDataProvider::ObjectTemperature | SensorTagDataProvider::AmbientTemperature | SensorTagDataProvider::Rotation);
- m_dataProviders.push_back(p);
- p = new MockDataProvider("MOCK_PROVIDER_2", this);
- p->setTagType(SensorTagDataProvider::Humidity | SensorTagDataProvider::Light | SensorTagDataProvider::Accelometer);
- m_dataProviders.push_back(p);
- p = new MockDataProvider("MOCK_PROVIDER_3", this);
- p->setTagType(SensorTagDataProvider::Magnetometer | SensorTagDataProvider::AirPressure);
- m_dataProviders.push_back(p);
- for (int i=0; i < m_dataProviders.length(); i++) {
- m_dataProviders.at(i)->startDataFetching();
- emit providerConnected(p->id());
- }
- // Stop scanning as we already have a provider
- finishScanning();
- }
- else {
- if (!m_initialized) {
- // Create a DataProvider objects early on for UI to be able to
- // show all data providers as available. The BT device information
- // will be added later on when the Bluetooth device has been found
- for (const QString& id : m_macFilters) {
- BluetoothDataProvider *p = new BluetoothDataProvider(id, this);
- m_dataProviders.push_back(p);
- // Set initial state to Scanning for UI to be
- // able to show "Connecting.." information
- p->setState(SensorTagDataProvider::Scanning);
- // Empty tag type, it will be set next
- p->setTagType(0);
- }
- // Fake that we have set of sensors with different capabilities
- // by assigning only some of the sensor data types to each sensor tag
- int i = 0;
- while (i < SensorTagDataProvider::tagTypeCount) {
- for (int p = 0; p < m_dataProviders.count() && i < SensorTagDataProvider::tagTypeCount; p++) {
- SensorTagDataProvider *provider = m_dataProviders.at(p);
- int tagType = provider->tagType() | (1 << i++);
- provider->setTagType(tagType);
- qCDebug(boot2QtDemos) << "Set tag type for provider" << provider->id() << "to" << QString::number(tagType, 2);
- }
- }
- emit dataProvidersChanged();
- m_initialized = true;
- }
- SensorTagDataProviderPool::startScanning();
- }
+ qDeleteAll(m_dataProviders);
+ m_dataProviders.clear();
+
+ MockDataProvider* p = new MockDataProvider("MOCK_PROVIDER_1", this);
+ p->setTagType(SensorTagDataProvider::ObjectTemperature | SensorTagDataProvider::AmbientTemperature | SensorTagDataProvider::Rotation);
+ m_dataProviders.push_back(p);
+ p = new MockDataProvider("MOCK_PROVIDER_2", this);
+ p->setTagType(SensorTagDataProvider::Humidity | SensorTagDataProvider::Light | SensorTagDataProvider::Accelometer);
+ m_dataProviders.push_back(p);
+ p = new MockDataProvider("MOCK_PROVIDER_3", this);
+ p->setTagType(SensorTagDataProvider::Magnetometer | SensorTagDataProvider::AirPressure);
+ m_dataProviders.push_back(p);
+ for (int i=0; i < m_dataProviders.length(); i++)
+ emit providerConnected(p->id());
+ // Stop scanning as we already have a provider
+ finishScanning();
+
+ SensorTagDataProviderPool::startScanning();
}
void DemoDataProviderPool::finishScanning()
{
- if (m_dataProviders.length() && m_mockData) {
- // For mock data we have only one provider and
- // it servers as the provider to the cloud, too
- m_cloudProvider = m_dataProviders.at(0);
- emit providerForCloudChanged();
- } else {
- m_cloudProvider = new DemoCloudProvider(this);
- static_cast<DemoCloudProvider*>(m_cloudProvider)->setDataProviders(m_dataProviders);
- for (SensorTagDataProvider *p : m_dataProviders) {
- // If BluetoothDevice object is not attached, the device has not been found
- if (!static_cast<BluetoothDataProvider*>(p)->device())
- p->setState(SensorTagDataProvider::NotFound);
- }
- }
emit dataProvidersChanged();
emit scanFinished();
}
diff --git a/tradeshow/iot-sensortag/demodataproviderpool.h b/tradeshow/iot-sensortag/demodataproviderpool.h
index 7d2e939..b6b6b53 100644
--- a/tradeshow/iot-sensortag/demodataproviderpool.h
+++ b/tradeshow/iot-sensortag/demodataproviderpool.h
@@ -62,8 +62,7 @@ public:
void startScanning() override;
- SensorTagDataProvider* providerForCloud() const override;
-
+ SensorTagDataProvider *providerForCloud() const override;
void setMockDataMode(bool mode);
protected:
@@ -71,7 +70,7 @@ protected:
private:
bool m_mockData;
- SensorTagDataProvider* m_cloudProvider;
+ SensorTagDataProvider *m_cloudProvider;
bool m_initialized;
};
@@ -82,7 +81,7 @@ class DemoCloudProvider : public SensorTagDataProvider
public:
explicit DemoCloudProvider(QObject *parent);
- void setDataProviders(const QList<SensorTagDataProvider*>& dataProviders);
+ void setDataProviders(const QList<SensorTagDataProvider *> &dataProviders);
QString sensorType() const override;
QString versionString() const override;
@@ -108,7 +107,7 @@ public:
float getRotationZ() const override;
float getAltitude() const override;
- QList<SensorTagDataProvider*> m_dataProviders;
+ QList<SensorTagDataProvider *> m_dataProviders;
};
#endif // DEMODATAPROVIDERPOOL_H
diff --git a/tradeshow/iot-sensortag/main.cpp b/tradeshow/iot-sensortag/main.cpp
index 5164919..34cdcfc 100644
--- a/tradeshow/iot-sensortag/main.cpp
+++ b/tradeshow/iot-sensortag/main.cpp
@@ -47,13 +47,6 @@
** $QT_END_LICENSE$
**
****************************************************************************/
-#include <QApplication>
-#include <QQmlApplicationEngine>
-#include <QQuickWindow>
-#include <QQmlContext>
-#include <QCommandLineParser>
-#include <QFontDatabase>
-#include <QScreen>
#if defined(RUNS_AS_HOST)
#include "bluetoothdataprovider.h"
@@ -67,77 +60,77 @@
#include "mockdataproviderpool.h"
#ifdef AZURE_UPLOAD
#include "cloudupdate.h"
+#elif defined (MQTT_UPLOAD)
+#include "mqttupdate.h"
+#include "mqttdataprovider.h"
+#include "mqttdataproviderpool.h"
#endif
#include "seriesstorage.h"
+#include <QApplication>
+#include <QQmlApplicationEngine>
+#include <QQuickWindow>
+#include <QQmlContext>
+#include <QCommandLineParser>
+#include <QFontDatabase>
+#include <QScreen>
+
Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos)
Q_LOGGING_CATEGORY(boot2QtDemos, "boot2qt.demos.iot")
+QString loggingOutput;
+QQuickWindow *loggingItem{nullptr};
+void handleMessageOutput(QtMsgType, const QMessageLogContext &, const QString &text)
+{
+ loggingOutput.prepend("\n");
+ loggingOutput.prepend(text);
+
+ loggingOutput.chop(loggingOutput.size() - 1024);
+ if (loggingItem)
+ loggingItem->setProperty("loggingOutput", loggingOutput);
+}
+
int main(int argc, char *argv[])
{
+ auto oldHandler = qInstallMessageHandler(handleMessageOutput);
+
// QtChars mandate using QApplication as it uses the graphics view fw
QApplication app(argc, argv);
QFontDatabase::addApplicationFont(QString::fromLatin1(":/resources/base/fonts/titilliumweb/TitilliumWeb-Regular.ttf"));
app.setFont(QFont("Titillium Web", 13));
- DataProviderPool *dataProviderPool = 0;
+ DataProviderPool *remoteProviderPool = nullptr;
+ DataProviderPool *localProviderPool = nullptr;
SeriesStorage seriesStorage;
QCommandLineParser parser;
- parser.addOptions({{"source", "Sensor data source", "cloud | sensor | mock"}, {"fullscreen", "Fullscreen mode", "true | false"}});
+ parser.addOptions({{"fullscreen", "Fullscreen mode", "true | false"}});
parser.addHelpOption();
parser.process(app);
- QString sensorSource = parser.value("source");
-
- if (sensorSource.isEmpty())
- sensorSource = qgetenv("QT_IOT_DEMO_DATA_SOURCE");
-
- if (sensorSource.isEmpty())
- // There is no wastorage package available for WinRT
-#ifdef Q_OS_WINRT
- sensorSource = "mock";
-#else
- sensorSource = "cloud";
+#if defined(MQTT_UPLOAD)
+ remoteProviderPool = new MqttDataProviderPool;
#endif
-
- if (sensorSource == QString("cloud").toLower()) {
- qCDebug(boot2QtDemos) << "Running in cloud mode";
- dataProviderPool = new CloudDataProviderPool;
- }
#if defined(RUNS_AS_HOST)
- else if (sensorSource == QString("sensor").toLower()) {
- qCDebug(boot2QtDemos) << "Running in sensor mode";
- dataProviderPool = new DemoDataProviderPool;
- // List of devices used in Embedded World
- static_cast<SensorTagDataProviderPool*>(dataProviderPool)->setMacFilterList(QStringList() <<
- "24:71:89:BF:3B:82" <<
- "24:71:89:BC:44:82");
- }
+// localProviderPool = new MockDataProviderPool;
+ localProviderPool = new DemoDataProviderPool;
#endif
- else if (sensorSource == QString("mock").toLower()){
- qCDebug(boot2QtDemos) << "Running in mock data mode";
- dataProviderPool = new MockDataProviderPool;
- }
- else {
- qCDebug(boot2QtDemos) << "Unknown mode: " << sensorSource;
- return 1;
- }
-
- seriesStorage.setDataProviderPool(dataProviderPool);
+ seriesStorage.setDataProviderPool(remoteProviderPool);
qmlRegisterType<SensorTagDataProvider>("SensorTag.DataProvider", 1, 0, "SensorTagData");
- qmlRegisterType<SensorTagDataProvider>("SensorTag.DataProvider", 1, 0, "ProviderState");
qmlRegisterType<DataProviderPool>("SensorTag.DataProvider", 1, 0, "DataProviderPool");
qmlRegisterType<SeriesStorage>("SensorTag.SeriesStorage", 1, 0, "SeriesStorage");
-#if defined(RUNS_AS_HOST) && defined(AZURE_UPLOAD)
+#if defined(RUNS_AS_HOST) && (defined(AZURE_UPLOAD) || defined(MQTT_UPLOAD))
+#if AZURE_UPLOAD
CloudUpdate update;
- if (sensorSource == "sensor" || sensorSource == "mock") {
- update.setDataProviderPool(dataProviderPool);
- update.restart();
- }
+# else
+ MqttUpdate update;
+# endif
+
+ update.setDataProviderPool(localProviderPool);
+ update.restart();
#endif
#ifdef DEPLOY_TO_FS
@@ -150,10 +143,15 @@ int main(int argc, char *argv[])
QString mainFile;
QUrl styleFile;
- QString uiVariant;
- bool fullScreen;
- int appWidth = 0;
- int appHeight = 0;
+ QString uiVariant = QStringLiteral("small");
+ bool fullScreen =
+#ifdef Q_OS_ANDROID
+ true;
+#else
+ false;
+#endif
+ int appWidth = 1920;
+ int appHeight = 1080;
QScreen* scr = qApp->screens().at(0);
@@ -169,35 +167,13 @@ int main(int argc, char *argv[])
addressString.append(QLatin1Char('/'));
}
}
-#if defined(UI_SMALL)
- mainFile = namingScheme + QStringLiteral("/resources/small/MainSmall.qml");
- styleFile = namingScheme + QStringLiteral("/resources/small/StyleSmall.qml");
-
- uiVariant = "small";
- fullScreen = true;
- appWidth = 1920;
- appHeight = 1080;
- qCDebug(boot2QtDemos) << "Using SMALL UI variant";
-#elif defined(UI_MEDIUM)
- mainFile =QStringLiteral("qrc:/resources/medium/MainMedium.qml");
- styleFile = QUrl("qrc:/resources/medium/StyleMedium.qml");
- uiVariant = "medium";
- fullScreen = true;
- appWidth = 1920;
- appHeight = 1080;
- qCDebug(boot2QtDemos) << "Using MEDIUM UI variant";
-#elif defined(UI_LARGE)
- mainFile = QStringLiteral("qrc:/resources/large/MainLarge.qml");
- styleFile = QUrl("qrc:/resources/large/StyleLarge.qml");
- uiVariant = "large";
- fullScreen = true;
- appWidth = 3840;
- appHeight = 2160;
- qCDebug(boot2QtDemos) << "Using LARGE UI variant";
+#ifdef UI_WATCH
+ mainFile = namingScheme + QStringLiteral("/resources/watch/MainWatch.qml");
+ styleFile = namingScheme + QStringLiteral("/resources/watch/StyleWatch.qml");
#else
-#error "Unknown UI form factor set in the project file"
+ mainFile = namingScheme + QStringLiteral("/resources/small/MainSmall.qml");
+ styleFile = namingScheme + QStringLiteral("/resources/small/StyleSmall.qml");
#endif
-
qmlRegisterSingletonType(styleFile, "Style", 1,0, "Style");
if (qEnvironmentVariableIsSet("QT_IOS_DEMO_NO_FULLSCREEN")) {
@@ -221,12 +197,17 @@ int main(int argc, char *argv[])
if (fullScreen)
item->showFullScreen();
- item->setProperty("dataProviderPool", QVariant::fromValue(dataProviderPool));
+ item->setProperty("localProviderPool", QVariant::fromValue(localProviderPool));
+ item->setProperty("remoteProviderPool", QVariant::fromValue(remoteProviderPool));
item->setProperty("contentFile", mainFile);
item->setProperty("seriesStorage", QVariant::fromValue(&seriesStorage));
item->setProperty("addresses", addressString);
+ loggingItem = item;
}
int returnValue = app.exec();
- dataProviderPool->stopScanning();
+ remoteProviderPool->stopScanning();
+
+ qInstallMessageHandler(oldHandler);
+
return returnValue;
}
diff --git a/tradeshow/iot-sensortag/mockdataprovider.cpp b/tradeshow/iot-sensortag/mockdataprovider.cpp
index 7e9607c..d8eb309 100644
--- a/tradeshow/iot-sensortag/mockdataprovider.cpp
+++ b/tradeshow/iot-sensortag/mockdataprovider.cpp
@@ -47,22 +47,23 @@
** $QT_END_LICENSE$
**
****************************************************************************/
+
#include "mockdataprovider.h"
#include <QtCore/QDateTime>
#define MOCK_DATA_SLOW_REFRESH_INTERVAL_MS 1000
#define MOCK_DATA_RAPID_REFRESH_INTERVAL_MS 200
-MockDataProvider::MockDataProvider(QString id, QObject* parent)
- : SensorTagDataProvider(id, parent),
- xAxisG(-0.02f),
- yAxisG(0.0f),
- zAxisG(0.02f),
- luxIncrease(100),
- rotationDegPerSecXIncrease(5),
- rotationDegPerSecYIncrease(7),
- rotationDegPerSecZIncrease(-9),
- m_smaSamples(0)
+MockDataProvider::MockDataProvider(QString id, QObject *parent)
+ : SensorTagDataProvider(id, parent)
+ , xAxisG(-0.02f)
+ , yAxisG(0.0f)
+ , zAxisG(0.02f)
+ , luxIncrease(100)
+ , rotationDegPerSecXIncrease(5)
+ , rotationDegPerSecYIncrease(7)
+ , rotationDegPerSecZIncrease(-9)
+ , m_smaSamples(0)
{
intervalRotation = MOCK_DATA_RAPID_REFRESH_INTERVAL_MS;
humidity = 40;
@@ -81,21 +82,22 @@ MockDataProvider::MockDataProvider(QString id, QObject* parent)
bool MockDataProvider::startDataFetching()
{
// Mock data is immediately available
- m_state = Connected;
+ setState(Connected);
qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
- slowUpdateTimer = new QTimer(this);
- connect(slowUpdateTimer, SIGNAL(timeout()), this, SLOT(slowTimerExpired()));
- slowUpdateTimer->start(MOCK_DATA_SLOW_REFRESH_INTERVAL_MS);
- rapidUpdateTimer = new QTimer(this);
- connect(rapidUpdateTimer, SIGNAL(timeout()), this, SLOT(rapidTimerExpired()));
- rapidUpdateTimer->start(MOCK_DATA_RAPID_REFRESH_INTERVAL_MS);
+ connect(&slowUpdateTimer, &QTimer::timeout,
+ this, &MockDataProvider::slowTimerExpired, Qt::UniqueConnection);
+ slowUpdateTimer.start(MOCK_DATA_SLOW_REFRESH_INTERVAL_MS);
+ connect(&rapidUpdateTimer, &QTimer::timeout,
+ this, &MockDataProvider::rapidTimerExpired, Qt::UniqueConnection);
+ rapidUpdateTimer.start(MOCK_DATA_RAPID_REFRESH_INTERVAL_MS);
return true;
}
void MockDataProvider::endDataFetching()
{
- slowUpdateTimer->stop();
+ slowUpdateTimer.stop();
+ rapidUpdateTimer.stop();
}
QString MockDataProvider::sensorType() const
@@ -235,7 +237,6 @@ void MockDataProvider::rapidTimerExpired()
void MockDataProvider::startServiceScan()
{
-
}
void MockDataProvider::reset()
diff --git a/tradeshow/iot-sensortag/mockdataprovider.h b/tradeshow/iot-sensortag/mockdataprovider.h
index 3555428..361db2e 100644
--- a/tradeshow/iot-sensortag/mockdataprovider.h
+++ b/tradeshow/iot-sensortag/mockdataprovider.h
@@ -49,7 +49,9 @@
****************************************************************************/
#ifndef MOCKDATAPROVIDER_H
#define MOCKDATAPROVIDER_H
+
#include "sensortagdataprovider.h"
+
#include <QtQml/QQmlEngine>
#include <QtQml/QJSEngine>
#include <QtCore/QTimer>
@@ -58,7 +60,7 @@ class MockDataProvider : public SensorTagDataProvider
{
Q_OBJECT
public:
- explicit MockDataProvider(QString id, QObject* parent = 0);
+ explicit MockDataProvider(QString id, QObject *parent = 0);
bool startDataFetching();
void endDataFetching();
@@ -75,8 +77,8 @@ protected:
void reset() override;
private:
- QTimer *slowUpdateTimer;
- QTimer *rapidUpdateTimer;
+ QTimer slowUpdateTimer;
+ QTimer rapidUpdateTimer;
float xAxisG;
float yAxisG;
float zAxisG;
diff --git a/tradeshow/iot-sensortag/mockdataproviderpool.cpp b/tradeshow/iot-sensortag/mockdataproviderpool.cpp
index 758cd99..6592e1b 100644
--- a/tradeshow/iot-sensortag/mockdataproviderpool.cpp
+++ b/tradeshow/iot-sensortag/mockdataproviderpool.cpp
@@ -69,12 +69,11 @@ void MockDataProviderPool::startScanning()
p->setTagType(SensorTagDataProvider::Humidity | SensorTagDataProvider::Light | SensorTagDataProvider::Accelometer);
m_dataProviders.push_back(p);
p = new MockDataProvider("MOCK_PROVIDER_3", this);
- p->setTagType(SensorTagDataProvider::Magnetometer | SensorTagDataProvider::AirPressure);
+ p->setTagType(SensorTagDataProvider::Magnetometer | SensorTagDataProvider::AirPressure | SensorTagDataProvider::Altitude);
m_dataProviders.push_back(p);
- for (int i=0; i < m_dataProviders.length(); i++) {
- m_dataProviders.at(i)->startDataFetching();
+ for (int i=0; i < m_dataProviders.length(); i++)
emit providerConnected(p->id());
- }
+
// Stop scanning as we already have a provider
finishScanning();
}
diff --git a/tradeshow/iot-sensortag/mockdataproviderpool.h b/tradeshow/iot-sensortag/mockdataproviderpool.h
index ba91d6c..5f47ff5 100644
--- a/tradeshow/iot-sensortag/mockdataproviderpool.h
+++ b/tradeshow/iot-sensortag/mockdataproviderpool.h
@@ -60,13 +60,13 @@ public:
void startScanning() override;
- SensorTagDataProvider* providerForCloud() const override;
+ SensorTagDataProvider *providerForCloud() const override;
protected:
void finishScanning();
private:
- SensorTagDataProvider* m_cloudProvider;
+ SensorTagDataProvider *m_cloudProvider;
};
#endif // MOCKDATAPROVIDERPOOL_H
diff --git a/tradeshow/iot-sensortag/mqttdataprovider.cpp b/tradeshow/iot-sensortag/mqttdataprovider.cpp
new file mode 100644
index 0000000..16bc096
--- /dev/null
+++ b/tradeshow/iot-sensortag/mqttdataprovider.cpp
@@ -0,0 +1,214 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 "mqttdataprovider.h"
+
+#include <QUrl>
+#include <QLoggingCategory>
+#include <QTimer>
+
+#define MAJOR_VERSION_NUMBER 1
+#define MINOR_VERSION_NUMBER 0
+
+Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos)
+
+MqttDataProvider::MqttDataProvider(QString id, QMqttClient *client, QObject *parent)
+ : SensorTagDataProvider(id, parent)
+ , m_client(client)
+ , m_subscription(nullptr)
+{
+ intervalRotation = 200;
+
+ m_pollTimer = new QTimer(this);
+ m_pollTimer->setInterval(intervalRotation);
+ m_pollTimer->setSingleShot(false);
+ connect(m_pollTimer, &QTimer::timeout, this, &MqttDataProvider::dataTimeout);
+}
+
+bool MqttDataProvider::startDataFetching()
+{
+ const QString subName = QString::fromLocal8Bit("sensors/%1/#").arg(m_id);
+
+ m_subscription = m_client->subscribe(subName);
+ connect(m_subscription, &QMqttSubscription::messageReceived,
+ this, &MqttDataProvider::messageReceived);
+ return true;
+}
+
+void MqttDataProvider::endDataFetching()
+{
+ if (m_subscription) {
+ disconnect(m_subscription, &QMqttSubscription::messageReceived,
+ this, &MqttDataProvider::messageReceived);
+ m_subscription->unsubscribe();
+ m_subscription = nullptr;
+ }
+}
+
+QString MqttDataProvider::sensorType() const
+{
+ return QString("mqtt data");
+}
+
+QString MqttDataProvider::versionString() const
+{
+ return QString::number(MAJOR_VERSION_NUMBER) + "." + QString::number(MINOR_VERSION_NUMBER);
+}
+
+void MqttDataProvider::reset()
+{
+}
+
+void MqttDataProvider::messageReceived(const QMqttMessage &msg)
+{
+ parseMessage(msg.payload(), msg.topic().name());
+ if (!m_pollTimer->isActive())
+ m_pollTimer->start();
+}
+
+void MqttDataProvider::parseMessage(const QString &content, const QString &topic)
+{
+ const QString msgType = topic.split(QLatin1Char('/')).last();
+ if (msgType == QStringLiteral("type")) {
+ qDebug() << "Type: " << content;
+ } else if (msgType == QStringLiteral("version")) {
+ qDebug() << "Version: " << content;
+ } else if (msgType == QStringLiteral("humid")) {
+ bool ok;
+ const double v = content.toDouble(&ok);
+ if (ok)
+ humidity = v;
+ } else if (msgType == QStringLiteral("light")) {
+ bool ok;
+ const double v = content.toDouble(&ok);
+ if (ok)
+ lightIntensityLux = v;
+ } else if (msgType == QStringLiteral("temperature")) {
+ //ambient_object
+ QString streamContent = content;
+ QTextStream stream(&streamContent);
+ double ambient, object;
+ char c;
+ stream >> ambient >> c >> object;
+
+ irAmbientTemperature = ambient;
+ irObjectTemperature = object;
+ } else if (msgType == QStringLiteral("barometer")) {
+ QString streamContent = content;
+ QTextStream stream(&streamContent);
+ double v1, v2;
+ char c;
+ stream >> v1 >> c >> v2;
+ barometerCelsiusTemperature = v1;
+ barometerHPa = v2;
+ } else if (msgType == QStringLiteral("gyro")) {
+ QString streamContent = content;
+ QTextStream stream(&streamContent);
+ double v1, v2, v3;
+ char c;
+ stream >> v1 >> c >> v2 >> c >> v3;
+ gyroscopeX_degPerSec = v1;
+ gyroscopeY_degPerSec = v2;
+ gyroscopeZ_degPerSec = v3;
+ } else if (msgType == QStringLiteral("accel")) {
+ QString streamContent = content;
+ QTextStream stream(&streamContent);
+ double v1, v2, v3;
+ char c;
+ stream >> v1 >> c >> v2 >> c >> v3;
+ accelometerX = v1;
+ accelometerY = v2;
+ accelometerZ = v3;
+ } else if (msgType == QStringLiteral("magnet")) {
+ QString streamContent = content;
+ QTextStream stream(&streamContent);
+ double v1, v2, v3;
+ char c;
+ stream >> v1 >> c >> v2 >> c >> v3;
+ magnetometerMicroT_xAxis = v1;
+ magnetometerMicroT_yAxis = v2;
+ magnetometerMicroT_zAxis = v3;
+ } else if (msgType == QStringLiteral("rotation")) {
+ QString streamContent = content;
+ QTextStream stream(&streamContent);
+ double v1, v2, v3;
+ char c;
+ stream >> v1 >> c >> v2 >> c >> v3;
+ rotation_x = v1;
+ rotation_y = v2;
+ rotation_z = v3;
+ } else if (msgType == QStringLiteral("altitude")) {
+ QString streamContent = content;
+ QTextStream stream(&streamContent);
+ float alt;
+ stream >> alt;
+ altitude = alt;
+ } else {
+ qWarning() << "Unknown sensor data received:" << topic;
+ }
+}
+
+void MqttDataProvider::dataTimeout()
+{
+ emit relativeHumidityChanged();
+ emit lightIntensityChanged();
+ emit infraredAmbientTemperatureChanged();
+ emit infraredObjectTemperatureChanged();
+ emit barometerCelsiusTemperatureChanged();
+ emit barometer_hPaChanged();
+ emit gyroscopeDegPerSecChanged();
+ emit accelometerChanged();
+ emit magnetometerMicroTChanged();
+ emit rotationXChanged();
+ emit rotationYChanged();
+ emit rotationZChanged();
+ emit altitudeChanged();
+ emit rotationValuesChanged();
+}
diff --git a/tradeshow/iot-sensortag/mqttdataprovider.h b/tradeshow/iot-sensortag/mqttdataprovider.h
new file mode 100644
index 0000000..9a52332
--- /dev/null
+++ b/tradeshow/iot-sensortag/mqttdataprovider.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 MQTTDATAPROVIDER_H
+#define MQTTDATAPROVIDER_H
+
+#include "sensortagdataprovider.h"
+#include <QtQml/QQmlEngine>
+#include <QtQml/QJSEngine>
+#include <QtMqtt/QMqttClient>
+#include <QtMqtt/QMqttSubscription>
+
+class QTimer;
+
+class MqttDataProvider : public SensorTagDataProvider
+{
+ Q_OBJECT
+public:
+ explicit MqttDataProvider(QString id, QMqttClient *client, QObject *parent = 0);
+
+ bool startDataFetching();
+ void endDataFetching();
+ QString sensorType() const;
+ QString versionString() const;
+ void reset() override;
+
+public slots:
+ void messageReceived(const QMqttMessage &msg);
+ void parseMessage(const QString &content, const QString &topic);
+ void dataTimeout();
+
+private:
+ QTimer *m_pollTimer;
+ QMqttClient *m_client;
+ QMqttSubscription *m_subscription;
+};
+#endif // MQTTDATAPROVIDER_H
diff --git a/tradeshow/iot-sensortag/mqttdataproviderpool.cpp b/tradeshow/iot-sensortag/mqttdataproviderpool.cpp
new file mode 100644
index 0000000..24ce47c
--- /dev/null
+++ b/tradeshow/iot-sensortag/mqttdataproviderpool.cpp
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 "mqttdataproviderpool.h"
+#include "mqttdataprovider.h"
+
+#include <QtCore/QDebug>
+
+MqttDataProviderPool::MqttDataProviderPool(QObject *parent)
+ : DataProviderPool(parent)
+ , m_client(new QMqttClient(this))
+{
+ m_poolName = "Mqtt";
+}
+
+void MqttDataProviderPool::startScanning()
+{
+ emit providerConnected("MQTT_CLOUD");
+ emit providersUpdated();
+ emit dataProvidersChanged();
+
+ m_client->setHostname(QLatin1String(MQTT_BROKER));
+ m_client->setPort(MQTT_PORT);
+ m_client->setUsername(QByteArray(MQTT_USERNAME));
+ m_client->setPassword(QByteArray(MQTT_PASSWORD));
+
+ connect(m_client, &QMqttClient::connected, [this]() {
+ auto sub = m_client->subscribe(QLatin1String("sensors/active"));
+ connect(sub, &QMqttSubscription::messageReceived, this, &MqttDataProviderPool::deviceUpdate);
+ });
+ connect(m_client, &QMqttClient::disconnected, [this]() {
+ qDebug() << "Pool client disconnected";
+ });
+ m_client->connectToHost();
+}
+
+void MqttDataProviderPool::deviceUpdate(const QMqttMessage &msg)
+{
+ static QSet<QString> knownDevices;
+ // Registration is: deviceName>Online
+ const QByteArrayList payload = msg.payload().split('>');
+ const QString deviceName = payload.first();
+ const QString deviceStatus = payload.at(1);
+ const QString subName = QString::fromLocal8Bit("sensors/%1/#").arg(deviceName);
+
+ bool updateRequired = false;
+ if (deviceStatus == QLatin1String("Online")) { // new device
+ // Skip local items
+ if (deviceName.startsWith(QSysInfo::machineHostName()))
+ return;
+
+ if (!knownDevices.contains(deviceName)) {
+ auto prov = new MqttDataProvider(deviceName, m_client, this);
+ prov->setState(SensorTagDataProvider::Connected);
+ m_dataProviders.push_back(prov);
+ if (m_currentProvider == nullptr)
+ setCurrentProviderIndex(m_dataProviders.size() - 1);
+ knownDevices.insert(deviceName);
+ updateRequired = true;
+ }
+ } else if (deviceStatus == QLatin1String("Offline")) { // device died
+ knownDevices.remove(deviceName);
+ updateRequired = true;
+ for (auto prov : m_dataProviders) {
+ if (prov->id() == deviceName) {
+ m_dataProviders.removeAll(prov);
+ break;
+ }
+ }
+ }
+
+ if (updateRequired) {
+ emit providersUpdated();
+ emit dataProvidersChanged();
+ }
+}
diff --git a/tradeshow/iot-sensortag/mqttdataproviderpool.h b/tradeshow/iot-sensortag/mqttdataproviderpool.h
new file mode 100644
index 0000000..1ba33ff
--- /dev/null
+++ b/tradeshow/iot-sensortag/mqttdataproviderpool.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 MQTTDATAPROVIDERPOOL_H
+#define MQTTDATAPROVIDERPOOL_H
+
+#include "dataproviderpool.h"
+#include <QtMqtt/QMqttClient>
+
+class MqttDataProvider;
+
+#define MQTT_BROKER ""
+#define MQTT_PORT 1883
+#define MQTT_USERNAME ""
+#define MQTT_PASSWORD ""
+
+class MqttDataProviderPool : public DataProviderPool
+{
+public:
+ explicit MqttDataProviderPool(QObject *parent = 0);
+
+ void startScanning() override;
+
+public Q_SLOTS:
+ void deviceUpdate(const QMqttMessage &msg);
+
+private:
+ QMqttClient *m_client;
+};
+
+#endif // MQTTDATAPROVIDERPOOL_H
diff --git a/tradeshow/iot-sensortag/mqttupdate.cpp b/tradeshow/iot-sensortag/mqttupdate.cpp
new file mode 100644
index 0000000..45ea238
--- /dev/null
+++ b/tradeshow/iot-sensortag/mqttupdate.cpp
@@ -0,0 +1,376 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 "mqttupdate.h"
+#include "cloudservice.h"
+#include "dataproviderpool.h"
+#include "sensortagdataprovider.h"
+#include "demodataproviderpool.h"
+#include "mqttdataproviderpool.h"
+
+#include <QtCore/QUuid>
+#include <QtMqtt/QtMqtt>
+#include <QSysInfo>
+
+#define ROUNDING_DECIMALS 2
+#define UPDATE_INTERVAL 100
+
+Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos)
+
+MqttUpdate::MqttUpdate(QObject *parent)
+ : QObject(parent)
+ , m_providerPool(0)
+#ifdef MQTT_TIMER_BASED_PUBLISH
+ , m_timerId(0)
+#endif
+ , m_handler(nullptr)
+ , m_updateInterval(UPDATE_INTERVAL)
+{
+}
+
+void MqttUpdate::setDataProviderPool(DataProviderPool *provider)
+{
+ m_providerPool = provider;
+ connect(m_providerPool, &DataProviderPool::currentProviderChanged, this, [=]() {
+ if (m_handler)
+ m_handler->deleteLater();
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+ if (!provider)
+ return;
+
+ QString hostname = QSysInfo::machineHostName();
+ if (hostname == QLatin1String("localhost")) {
+ hostname = QUuid::createUuid().toString();
+ qCDebug(boot2QtDemos) << "localhost not unique. New ID:" << hostname;
+ }
+
+ m_handler = new MqttEventHandler(hostname + QLatin1String(" - ") + provider->id());
+ m_handler->m_providerPool = m_providerPool;
+
+ connect(provider, &SensorTagDataProvider::gyroscopeDegPerSecChanged, m_handler, &MqttEventHandler::uploadGyro);
+ connect(provider, &SensorTagDataProvider::accelometerChanged, m_handler, &MqttEventHandler::uploadAccelerometer);
+ connect(provider, &SensorTagDataProvider::rotationXChanged, m_handler, &MqttEventHandler::uploadRotation);
+ connect(provider, &SensorTagDataProvider::infraredAmbientTemperatureChanged, m_handler, &MqttEventHandler::uploadTemperature);
+ connect(provider, &SensorTagDataProvider::magnetometerMicroTChanged, m_handler, &MqttEventHandler::uploadMagnetometer);
+ connect(provider, &SensorTagDataProvider::relativeHumidityChanged, m_handler, &MqttEventHandler::uploadHumidity);
+ connect(provider, &SensorTagDataProvider::lightIntensityChanged, m_handler, &MqttEventHandler::uploadLight);
+ connect(provider, &SensorTagDataProvider::barometer_hPaChanged, m_handler, &MqttEventHandler::uploadBarometer);
+ connect(provider, &SensorTagDataProvider::altitudeChanged, m_handler, &MqttEventHandler::uploadAltitude);
+ });
+}
+
+int MqttUpdate::updateInterval() const
+{
+ return m_updateInterval;
+}
+
+void MqttUpdate::setUpdateInterval(int interval)
+{
+ m_updateInterval = interval;
+}
+
+void MqttUpdate::restart()
+{
+#ifdef MQTT_TIMER_BASED_PUBLISH
+ killTimer(m_timerId);
+ m_timerId = startTimer(m_updateInterval);
+#endif
+}
+
+void MqttUpdate::stop()
+{
+#ifdef MQTT_TIMER_BASED_PUBLISH
+ killTimer(m_timerId);
+#endif
+}
+
+#ifndef MQTT_TIMER_BASED_PUBLISH
+
+inline bool ensureConnected(QMqttClient *client)
+{
+ if (client->state() == QMqttClient::Connected)
+ return true;
+
+ if (client->state() == QMqttClient::Disconnected) {
+ qCDebug(boot2QtDemos) << "Disconnected, need to reconnect";
+ client->connectToHost();
+ }
+
+ return false; // Connecting, nothing to do
+}
+
+void MqttEventHandler::uploadGyro()
+{
+ if (!ensureConnected(m_client))
+ return;
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider) {
+ const QString gyro = QString("%1_%2_%3").arg(provider->getGyroscopeX_degPerSec(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getGyroscopeY_degPerSec(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getGyroscopeZ_degPerSec(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("gyro"), gyro.toLocal8Bit());
+ }
+}
+
+void MqttEventHandler::uploadTemperature()
+{
+ if (!ensureConnected(m_client))
+ return;
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider) {
+ const QString temperature = QString("%1_%2").arg(provider->getInfraredAmbientTemperature(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getInfraredObjectTemperature(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("temperature"), temperature.toLocal8Bit());
+ }
+}
+
+void MqttEventHandler::uploadHumidity()
+{
+ if (!ensureConnected(m_client))
+ return;
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider)
+ m_client->publish(m_topicPrefix + QLatin1String("humid"),
+ QString("%1").arg(provider->getRelativeHumidity(), 0, 'f', ROUNDING_DECIMALS).toLocal8Bit());
+}
+
+void MqttEventHandler::uploadLight()
+{
+ if (!ensureConnected(m_client))
+ return;
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider)
+ m_client->publish(m_topicPrefix + QLatin1String("light"),
+ QString("%1").arg(provider->getLightIntensityLux(), 0, 'f', ROUNDING_DECIMALS).toLocal8Bit());
+}
+
+void MqttEventHandler::uploadAltitude()
+{
+ if (!ensureConnected(m_client))
+ return;
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider)
+ m_client->publish(m_topicPrefix + QLatin1String("altitude"),
+ QString("%1").arg(provider->getAltitude(), 0, 'f', ROUNDING_DECIMALS).toLocal8Bit());
+}
+
+void MqttEventHandler::uploadBarometer()
+{
+ if (!ensureConnected(m_client))
+ return;
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider) {
+ const QString baro = QString("%1_%2").arg(provider->getBarometerCelsiusTemperature(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getBarometer_hPa(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("barometer"), baro.toLocal8Bit());
+ }
+}
+
+void MqttEventHandler::uploadAccelerometer()
+{
+ if (!ensureConnected(m_client))
+ return;
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider) {
+ const QString accel = QString("%1_%2_%3").arg(provider->getAccelometer_xAxis(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getAccelometer_yAxis(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getAccelometer_zAxis(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("accel"), accel.toLocal8Bit());
+ }
+}
+
+void MqttEventHandler::uploadMagnetometer()
+{
+ if (!ensureConnected(m_client))
+ return;
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider) {
+ const QString magnet = QString("%1_%2_%3").arg(provider->getMagnetometerMicroT_xAxis(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getMagnetometerMicroT_yAxis(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getMagnetometerMicroT_zAxis(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("magnet"), magnet.toLocal8Bit());
+ }
+}
+
+void MqttEventHandler::uploadRotation()
+{
+ if (!ensureConnected(m_client))
+ return;
+
+ SensorTagDataProvider *provider = m_providerPool->currentProvider();
+
+ if (provider) {
+ const QString rotation = QString("%1_%2_%3").arg(provider->getRotationX(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getRotationY(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(provider->getRotationZ(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("rotation"), rotation.toLocal8Bit());
+ }
+}
+
+void MqttEventHandler::clientConnected()
+{
+ m_pingTimer.start();
+}
+
+void MqttEventHandler::sendAlive()
+{
+ m_client->publish(QLatin1String("sensors/active"), QString::fromLocal8Bit("%1>Online").arg(m_deviceName).toLocal8Bit(), 1);
+}
+#endif // !MQTT_TIMER_BASED_PUBLISH
+
+#ifdef MQTT_TIMER_BASED_PUBLISH
+void MqttUpdate::timerEvent(QTimerEvent *event)
+{
+ Q_UNUSED(event);
+
+ if (!m_provider)
+ return;
+
+ writeToCloud();
+}
+
+void MqttUpdate::writeToCloud()
+{
+ if (!m_provider) {
+ qWarning("MqttUpdate: sensor data provider not set. Data not updated");
+ return;
+ }
+
+ if (m_client->state() == QMqttClient::Disconnected) {
+ qCDebug(boot2QtDemos) << "Disconnected, need to reconnect";
+ m_client->connectToHost();
+ }
+ if (m_client->state() == QMqttClient::Connected) {
+ static bool sendTypeVersion = true;
+ if (sendTypeVersion) {
+ m_client->publish(m_topicPrefix + QLatin1String("type"), m_provider->sensorType());
+ m_client->publish(m_topicPrefix + QLatin1String("version"), m_provider->versionString());
+ sendTypeVersion = false;
+ }
+ m_client->publish(m_topicPrefix + QLatin1String("humid"),
+ QString("%1").arg(m_provider->getRelativeHumidity(), 0, 'f', ROUNDING_DECIMALS));
+ m_client->publish(m_topicPrefix + QLatin1String("light"),
+ QString("%1").arg(m_provider->getLightIntensityLux(), 0, 'f', ROUNDING_DECIMALS));
+
+ const QString temperature = QString("%1_%2").arg(m_provider->getInfraredAmbientTemperature(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getInfraredObjectTemperature(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("temperature"), temperature);
+
+ const QString baro = QString("%1_%2").arg(m_provider->getBarometerCelsiusTemperature(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getBarometer_hPa(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("barometer"), baro);
+
+ const QString gyro = QString("%1_%2_%3").arg(m_provider->getGyroscopeX_degPerSec(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getGyroscopeY_degPerSec(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getGyroscopeZ_degPerSec(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("gyro"), gyro);
+
+ const QString accel = QString("%1_%2_%3").arg(m_provider->getAccelometer_xAxis(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getAccelometer_yAxis(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getAccelometer_zAxis(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("accel"), accel);
+
+ const QString magnet = QString("%1_%2_%3").arg(m_provider->getMagnetometerMicroT_xAxis(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getMagnetometerMicroT_yAxis(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getMagnetometerMicroT_zAxis(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("magnet"), magnet);
+
+ const QString rotation = QString("%1_%2_%3").arg(m_provider->getRotationX(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getRotationY(), 0, 'f', ROUNDING_DECIMALS)
+ .arg(m_provider->getRotationZ(), 0, 'f', ROUNDING_DECIMALS);
+ m_client->publish(m_topicPrefix + QLatin1String("rotation"), rotation);
+ }
+}
+#endif //MQTT_TIMER_BASED_PUBLISH
+
+MqttEventHandler::MqttEventHandler(const QString &name, QObject *parent)
+ : QObject(parent)
+{
+ m_deviceName = name;
+ m_topicPrefix = QString::fromLocal8Bit("sensors/%1/").arg(m_deviceName);
+
+ m_pingTimer.setInterval(5000);
+ m_pingTimer.setSingleShot(false);
+ connect(&m_pingTimer, &QTimer::timeout, this, &MqttEventHandler::sendAlive);
+
+ m_client = new QMqttClient;
+ m_client->setHostname(QLatin1String(MQTT_BROKER));
+ m_client->setPort(MQTT_PORT);
+ m_client->setUsername(QByteArray(MQTT_USERNAME));
+ m_client->setPassword(QByteArray(MQTT_PASSWORD));
+
+ m_client->setWillMessage(QString::fromLocal8Bit("%1>Offline").arg(m_deviceName).toLocal8Bit());
+ m_client->setWillQoS(1);
+ m_client->setWillRetain(true);
+ m_client->setWillTopic(QString::fromLocal8Bit("sensors/active"));
+ connect(m_client, &QMqttClient::connected, this, &MqttEventHandler::clientConnected);
+}
+
+MqttEventHandler::~MqttEventHandler()
+{
+ m_client->publish(QLatin1String("sensors/active"),
+ QString::fromLocal8Bit("%1>Offline").arg(m_deviceName).toLocal8Bit(), 1);
+}
diff --git a/tradeshow/iot-sensortag/mqttupdate.h b/tradeshow/iot-sensortag/mqttupdate.h
new file mode 100644
index 0000000..2c2d424
--- /dev/null
+++ b/tradeshow/iot-sensortag/mqttupdate.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 MQTTUPDATE_H
+#define MQTTUPDATE_H
+
+#include <QObject>
+#include <QTimer>
+
+class DataProviderPool;
+class SensorTagDataProvider;
+class QMqttClient;
+
+//#define MQTT_TIMER_BASED_PUBLISH 1
+
+class MqttEventHandler : public QObject
+{
+ Q_OBJECT
+public:
+ explicit MqttEventHandler(const QString &name, QObject *parent = 0);
+ ~MqttEventHandler();
+ DataProviderPool *m_providerPool;
+public slots:
+ void uploadGyro();
+ void uploadTemperature();
+ void uploadHumidity();
+ void uploadLight();
+ void uploadAltitude();
+ void uploadBarometer();
+ void uploadAccelerometer();
+ void uploadMagnetometer();
+ void uploadRotation();
+
+ void clientConnected();
+ void sendAlive();
+
+private:
+ QString m_deviceName;
+ QString m_topicPrefix;
+ QMqttClient *m_client;
+ QTimer m_pingTimer;
+};
+
+class MqttUpdate : public QObject
+{
+ Q_OBJECT
+public:
+ explicit MqttUpdate(QObject *parent = 0);
+
+ void setDataProviderPool(DataProviderPool *provider);
+
+ int updateInterval() const;
+ void setUpdateInterval(int interval);
+ void restart();
+ void stop();
+
+#ifdef MQTT_TIMER_BASED_PUBLISH
+protected:
+ void timerEvent(QTimerEvent* event);
+ virtual void writeToCloud();
+#else
+#endif
+
+protected:
+ DataProviderPool *m_providerPool;
+private:
+ QThread *m_handlerThread;
+ MqttEventHandler *m_handler;
+ int m_updateInterval;
+#ifdef MQTT_TIMER_BASED_PUBLISH
+ int m_timerId;
+#endif
+};
+
+#endif // MQTTUPDATE_H
diff --git a/tradeshow/iot-sensortag/resources/base/AccelChart.qml b/tradeshow/iot-sensortag/resources/base/AccelChart.qml
index 10a7758..d037ff1 100644
--- a/tradeshow/iot-sensortag/resources/base/AccelChart.qml
+++ b/tradeshow/iot-sensortag/resources/base/AccelChart.qml
@@ -90,6 +90,7 @@ BaseChart {
anchors.centerIn: parent
text: (index == 0) ? acceXValue : ((index == 1) ? acceYValue : acceZValue)
color: "white"
+ font.pixelSize: 26
}
}
diff --git a/tradeshow/iot-sensortag/resources/base/AltitudeChart.qml b/tradeshow/iot-sensortag/resources/base/AltitudeChart.qml
index 2c05bc7..20a8a0e 100644
--- a/tradeshow/iot-sensortag/resources/base/AltitudeChart.qml
+++ b/tradeshow/iot-sensortag/resources/base/AltitudeChart.qml
@@ -152,6 +152,7 @@ BaseChart {
anchors.centerIn: parent
anchors.horizontalCenterOffset: 74
color: "white"
+ font.pixelSize: 26
}
}
}
diff --git a/tradeshow/iot-sensortag/resources/base/BaseChart.qml b/tradeshow/iot-sensortag/resources/base/BaseChart.qml
index d144916..bc850fc 100644
--- a/tradeshow/iot-sensortag/resources/base/BaseChart.qml
+++ b/tradeshow/iot-sensortag/resources/base/BaseChart.qml
@@ -54,7 +54,7 @@ import SensorTag.DataProvider 1.0
Item {
id: baseChart
- property var sensor: null
+ property var sensor: singleSensorSource
property string title
property alias contentItem: loader.item
property alias content: loader.sourceComponent
@@ -74,7 +74,7 @@ Item {
Image {
id: titleIcon
-
+ visible: Style.uiStyle == "small"
anchors.top: parent.top
source: pathPrefix + "General/icon_sensor.png"
}
@@ -148,6 +148,7 @@ Item {
axis.z: 0
angle: rightSide ? 180 : 0
}
+ visible: Style.uiStyle == "small"
}
MouseArea {
diff --git a/tradeshow/iot-sensortag/resources/base/GyroChart.qml b/tradeshow/iot-sensortag/resources/base/GyroChart.qml
index 78ee163..f09ef6b 100644
--- a/tradeshow/iot-sensortag/resources/base/GyroChart.qml
+++ b/tradeshow/iot-sensortag/resources/base/GyroChart.qml
@@ -92,7 +92,6 @@ BaseChart {
anchors.left: parent.left
anchors.leftMargin: -20
anchors.right: parent.right
- anchors.rightMargin: -15
antialiasing: true
backgroundColor: "transparent"
legend.visible: false
@@ -160,6 +159,7 @@ BaseChart {
color: "white"
anchors.left: parent.left
anchors.bottom: parent.bottom
+ font.pixelSize: 26
}
Text {
@@ -171,6 +171,7 @@ BaseChart {
anchors.left: coordText.right
anchors.leftMargin: 16
anchors.bottom: parent.bottom
+ font.pixelSize: 26
}
}
}
diff --git a/tradeshow/iot-sensortag/resources/base/HumidityChart.qml b/tradeshow/iot-sensortag/resources/base/HumidityChart.qml
index 60d1ee7..869288a 100644
--- a/tradeshow/iot-sensortag/resources/base/HumidityChart.qml
+++ b/tradeshow/iot-sensortag/resources/base/HumidityChart.qml
@@ -48,8 +48,6 @@
**
****************************************************************************/
import QtQuick 2.5
-import QtQuick.Controls 2.0
-import QtQuick.Controls.Styles 1.4
import QtCharts 2.1
import SensorTag.DataProvider 1.0
import QtQuick.Extras 1.4
@@ -104,6 +102,7 @@ BaseChart {
text: "Max\n" + maxHumi.toFixed(0) + " %"
lineHeight: 0.8
color: "white"
+ font.pixelSize: 26
}
Text {
@@ -113,6 +112,7 @@ BaseChart {
text: "Min\n" + minHumi.toFixed(0) + " %"
lineHeight: 0.8
color: "white"
+ font.pixelSize: 26
}
}
}
diff --git a/tradeshow/iot-sensortag/resources/base/MagnetometerChart.qml b/tradeshow/iot-sensortag/resources/base/MagnetometerChart.qml
index cf4a409..2b2ccac 100644
--- a/tradeshow/iot-sensortag/resources/base/MagnetometerChart.qml
+++ b/tradeshow/iot-sensortag/resources/base/MagnetometerChart.qml
@@ -57,7 +57,6 @@ BaseChart {
property int maxNumOfMagnReadings: 24
- readonly property color chartColor: "#15bdff"
readonly property string xColor: "#4db300"
readonly property string yColor: "white"
readonly property string zColor: "#f64405"
@@ -154,6 +153,7 @@ BaseChart {
color: (index == 0) ? xColor : ((index == 1) ? yColor : zColor)
anchors.left: parent.left
anchors.bottom: parent.bottom
+ font.pixelSize: 26
}
Text {
@@ -165,6 +165,7 @@ BaseChart {
anchors.left: coordText.right
anchors.leftMargin: 16
anchors.bottom: parent.bottom
+ font.pixelSize: 26
}
}
}
diff --git a/tradeshow/iot-sensortag/resources/base/ObjectTemperatureChart.qml b/tradeshow/iot-sensortag/resources/base/ObjectTemperatureChart.qml
index ffba32b..a594257 100644
--- a/tradeshow/iot-sensortag/resources/base/ObjectTemperatureChart.qml
+++ b/tradeshow/iot-sensortag/resources/base/ObjectTemperatureChart.qml
@@ -89,6 +89,7 @@ BaseChart {
anchors.centerIn: gauge
anchors.horizontalCenterOffset: -120
anchors.verticalCenterOffset: 60
+ font.pixelSize: 26
}
Text {
@@ -107,6 +108,7 @@ BaseChart {
anchors.centerIn: gauge
anchors.horizontalCenterOffset: 120
anchors.verticalCenterOffset: -30
+ font.pixelSize: 26
}
}
}
diff --git a/tradeshow/iot-sensortag/resources/base/RotationPage.qml b/tradeshow/iot-sensortag/resources/base/RotationPage.qml
index eee47c1..a153c80 100644
--- a/tradeshow/iot-sensortag/resources/base/RotationPage.qml
+++ b/tradeshow/iot-sensortag/resources/base/RotationPage.qml
@@ -53,7 +53,7 @@ import SensorTag.DataProvider 1.0
Item {
id: dialerRoot
- property var sensor
+ property var sensor : singleSensorSource
property var rotationUpdateInterval: sensor ? sensor.rotationUpdateInterval : 0
property int sensorState: sensor ? sensor.state : SensorTagData.Disconnected
diff --git a/tradeshow/iot-sensortag/resources/base/SensorSettings.qml b/tradeshow/iot-sensortag/resources/base/SensorSettings.qml
index c9c326a..5302816 100644
--- a/tradeshow/iot-sensortag/resources/base/SensorSettings.qml
+++ b/tradeshow/iot-sensortag/resources/base/SensorSettings.qml
@@ -53,152 +53,162 @@ import Style 1.0
import SensorTag.DataProvider 1.0
Rectangle {
- id: mainRect
+ id: sourceSelector
+ property alias sensorCount : sensorListView.count
+ property color selectedBackgroundColor : "#15bdff"
+ property color deselectedBackgroundColor: "transparent"
+
+ color: "transparent"
+ width: 800
+ height: 800
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ function createSensorConnection() {
+ clickBait.deactivate()
+
+ var currentPool = getCurrentPool();
+ if (currentPool.dataProviders[sensorListView.currentIndex] === singleSensorSource) {
+ console.log("Same data provider selected, nothing to change...")
+ return;
+ }
- property alias listModelCount: list.count
+ if (singleSensorSource)
+ singleSensorSource.endDataFetching();
+ // UI gets information about the intended setup of the
+ // sensor even though they have not been really discovered yet
+ if (currentPool) {
+ singleSensorSource = currentPool.dataProviders[sensorListView.currentIndex]
+ currentPool.currentProviderIndex = sensorListView.currentIndex
- width: 620
- height: 480
- color: "black"
+ seriesStorage.setDataProviderPool(currentPool);
+ seriesStorage.dataProviderPoolChanged();
- Text {
- id: titleText
- color: "white"
- text: "SENSOR SETTINGS"
- font.pixelSize: Style.indicatorTitleFontSize
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- anchors.topMargin: 8
+ singleSensorSource.startDataFetching()
+ }
}
Image {
- id: icon
-
- anchors.top: titleText.bottom
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.margins: 8
- source: pathPrefix + "Toolbar/icon_topbar_sensor.png"
+ source: "images/bg_blue.jpg"
+ anchors.fill: parent
}
- ListView {
- id: list
- anchors.top: icon.bottom
- anchors.topMargin: 16
- anchors.left: parent.left
- anchors.leftMargin: 30
- anchors.right: parent.right
- anchors.rightMargin: 30
- orientation: ListView.Horizontal
- model: dataProviderPool.dataProviders
- height: parent.height
- clip: true
- snapMode: ListView.SnapToItem
- boundsBehavior: Flickable.StopAtBounds
-
- function getTagTypeStr(tagType) {
- var tagStr = "";
- if (tagType & SensorTagData.AmbientTemperature)
- tagStr += "Ambient Temperature\n";
- if (tagType & SensorTagData.ObjectTemperature)
- tagStr += "Object Temperature\n";
- if (tagType & SensorTagData.Humidity)
- tagStr += "Humidity\n";
- if (tagType & SensorTagData.Altitude)
- tagStr += "Altitude\n";
- if (tagType & SensorTagData.Light)
- tagStr += "Light\n";
- if (tagType & SensorTagData.Rotation)
- tagStr += "Gyroscope\n";
- if (tagType & SensorTagData.Magnetometer)
- tagStr += "Magnetometer\n";
- if (tagType & SensorTagData.Accelometer)
- tagStr += "Accelometer\n";
-
- return tagStr;
+ Image {
+ id: separator
+ source: pathPrefix + "General/separator.png"
+ anchors.top: parent.top
+ width: parent.width
+ transform: Rotation {
+ origin.x: separator.width / 2
+ origin.y: separator.height / 2
+ angle: 180
}
+ }
- delegate: Item {
- id: listItem
- width: mainRect.width / 3
- height: childrenRect.height
-
- ColumnLayout {
- spacing: 8
-
- Text {
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- horizontalAlignment: Text.AlignHCenter
- text: providerId
- color: "white"
- font.pixelSize: 16
- elide: Text.ElideMiddle
- }
-
- BlinkingIcon {
- id: sensorIcon
-
- property bool canBlink: modelData.state === SensorTagData.Scanning
-
- onCanBlinkChanged: {
- if (canBlink)
- sensorIcon.startBlinking();
- else
- sensorIcon.stopBlinking();
- }
-
- source: pathPrefix + "Toolbar/icon_topbar_sensor.png"
- anchors.horizontalCenter: parent.horizontalCenter
-
- Component.onDestruction: {
- sensorIcon.stopBlinking()
- }
-
- MouseArea {
- anchors.fill: parent
- onClicked: {
- if (modelData.state === SensorTagData.Connected)
- dataProviderPool.disconnectProvider(modelData.providerId);
- else if (modelData.state === SensorTagData.NotFound)
- dataProviderPool.startScanning();
- else if (modelData.state === SensorTagData.Scanning)
- dataProviderPool.disconnectProvider(modelData.providerId)
- else
- modelData.startServiceScan();
- }
- }
- }
+ Rectangle {
+ id: buttonRect
+ anchors.top: separator.bottom
+ anchors.margins: 20
+ color: "transparent"
+ width: parent.width - 20
+ height: 40
+
+ Rectangle {
+ border.color: "white"
+ color: localSelected ? sourceSelector.selectedBackgroundColor : sourceSelector.deselectedBackgroundColor
+ anchors.top: parent.top
+ anchors.left: parent.left
+ width: parent.width / 2
+ height: 30
+ Text {
+ text: "Local"
+ anchors.centerIn: parent
+ color: "white"
+ font.pixelSize: 26
+ }
+ }
- Text {
- Layout.alignment: Qt.AlignHCenter
- text: modelData.state === SensorTagData.NotFound ? "\nNOT FOUND"
- : (modelData.state === SensorTagData.Disconnected) ? "\nDISCONNECTED"
- : (modelData.state === SensorTagData.Scanning) ? "\nCONNECTING"
- : (modelData.state === SensorTagData.Connected) ? "\nCONNECTED"
- : "Error"
- color: "white"
- font.pixelSize: 14
- }
+ Rectangle {
+ border.color: "white"
+ color: !localSelected ? sourceSelector.selectedBackgroundColor : sourceSelector.deselectedBackgroundColor
+ anchors.top: parent.top
+ anchors.right: parent.right
+ width: parent.width / 2
+ height: 30
+ Text {
+ text: "Remote"
+ anchors.centerIn: parent
+ color: "white"
+ font.pixelSize: 26
+ }
+ }
- Item {
- height: 30
- width: 10
- }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: localSelected = !localSelected
+ }
+ }
- Text {
- color: "white"
- text: "Provides data for:"
- font.pixelSize: 14
- }
+ ListView {
+ id: sensorListView
+ model: localSelected ? (localProviderPool ? localProviderPool.dataProviders : 0)
+ : (remoteProviderPool ? remoteProviderPool.dataProviders : 0)
+
+ width: buttonRect.width
+ anchors.top: buttonRect.bottom
+ anchors.bottom: connectButton.top
+ focus: true
+ clip: true
- Text {
- color: "white"
- lineHeight: 0.7
- text: list.getTagTypeStr(modelData.tagType())
- font.pixelSize: 14
+ delegate: Rectangle {
+ border.color: "white"
+ color: ListView.isCurrentItem ? sourceSelector.selectedBackgroundColor
+ : sourceSelector.deselectedBackgroundColor
+ radius: 5
+ height: 30
+ width: parent.width
+ Text {
+ text: providerId
+ anchors.centerIn: parent
+ color: "white"
+ font.pixelSize: 26
+ }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: sensorListView.currentIndex = index
+ onDoubleClicked: {
+ sensorListView.currentIndex = index
+ createSensorConnection()
}
}
}
}
+
+ Rectangle {
+ id: connectButton
+ width: buttonRect.width / 2
+ height: 30
+ anchors.bottom: bottomSeparator.top
+ anchors.right: parent.right
+ color: sensorListView.currentIndex != -1 ? sourceSelector.selectedBackgroundColor : "transparent"
+ border.color: "white"
+ Text {
+ text: "Connect"
+ anchors.centerIn: parent
+ color: "white"
+ font.pixelSize: 26
+ }
+ MouseArea {
+ id: connectButtonArea
+ anchors.fill: parent
+ enabled: sensorListView.currentIndex != -1
+ onClicked: createSensorConnection()
+ }
+ }
+
+ Image {
+ id: bottomSeparator
+ source: pathPrefix + "General/separator.png"
+ anchors.bottom: parent.bottom
+ width: parent.width
+ }
}
diff --git a/tradeshow/iot-sensortag/resources/base/TemperatureChart.qml b/tradeshow/iot-sensortag/resources/base/TemperatureChart.qml
index dbad24b..7a90dea 100644
--- a/tradeshow/iot-sensortag/resources/base/TemperatureChart.qml
+++ b/tradeshow/iot-sensortag/resources/base/TemperatureChart.qml
@@ -106,6 +106,7 @@ BaseChart {
anchors.horizontalCenter: reading.horizontalCenter
anchors.top: parent.top
color: "white"
+ font.pixelSize: 26
}
Image {
@@ -132,6 +133,7 @@ BaseChart {
color: "white"
anchors.bottom: parent.bottom
anchors.horizontalCenter: reading.horizontalCenter
+ font.pixelSize: 26
}
Text {
@@ -143,6 +145,7 @@ BaseChart {
color: "white"
anchors.right: parent.right
anchors.verticalCenter: reading.verticalCenter
+ font.pixelSize: 26
}
}
diff --git a/tradeshow/iot-sensortag/resources/base/TopToolbar.qml b/tradeshow/iot-sensortag/resources/base/TopToolbar.qml
index fd5054d..bb0ecf9 100644
--- a/tradeshow/iot-sensortag/resources/base/TopToolbar.qml
+++ b/tradeshow/iot-sensortag/resources/base/TopToolbar.qml
@@ -57,63 +57,45 @@ Item {
height: 100
width: implicitWidth
- CloudSettings {
- id: cloudSettings
-
- x: cloudItem.x
- y: topToolbar.height
- visible: false
- }
+ property alias topbar: topbar
+ property alias date: dateLabel
SensorSettings {
id: sensorList
x: sensorItem.x
y: topToolbar.height
- visible: false
+ width: Math.min(mainWindow.width, 800)
+ height: mainWindow.height - topToolbar.height
+ visible: true
}
- Item {
- id: cloudItem
-
- height: topToolbar.height
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.leftMargin: 8
- width: icon.width + cloudText.width + 3 * anchors.leftMargin
- opacity: dataProviderPool.name === "Cloud" || dataProviderPool.name === "Demo" ? 1.0 : 0.0
-
- Image {
- id: icon
- width: 58
- height: 40
- anchors.top: parent.top
- anchors.margins: 8
- source: pathPrefix + "Toolbar/icon_topbar_cloud.png"
- }
-
+ Image {
+ id: logWindow
+ source: "images/bg_blue.jpg"
+ x: sensorItem.x
+ y: topToolbar.height
+ width: Math.min(mainWindow.width, 600)
+ height: Math.min(mainWindow.height - topToolbar.height, 400)
+ anchors.horizontalCenter: parent.horizontalCenter
+ visible: false
Text {
- id: cloudText
+ clip: true
color: "white"
- text: "CLOUD"
- width: contentWidth
- font.pixelSize: Style.topToolbarSmallFontSize
- anchors.verticalCenter: icon.verticalCenter
- anchors.left: icon.right
- anchors.margins: 8
- }
-
- MouseArea {
+ font.pixelSize: 26
+ text: mainWindow.loggingOutput ? mainWindow.loggingOutput : "...debug..."
anchors.fill: parent
- onClicked: clickBait.activate(cloudSettings)
+ anchors.margins: 15
}
}
+
Item {
id: sensorItem
height: topToolbar.height
anchors.top: parent.top
- anchors.left: cloudItem.right
+ anchors.left: parent.left //cloudItem.right
+ anchors.leftMargin: 8
width: sensorIcon.width + sensorButton.width + 3 * anchors.leftMargin
Image {
@@ -127,7 +109,7 @@ Item {
Text {
anchors.centerIn: parent
- text: sensorList.listModelCount
+ text: sensorList.sensorCount
color: "white"
font.pixelSize: Style.topToolbarSmallFontSize
}
@@ -151,6 +133,7 @@ Item {
}
Text {
+ id: dateLabel
property bool showAddress : false
text: showAddress ? mainWindow.addresses : Qt.formatDateTime(new Date, "dddd, MMMM d, yyyy")
color: "white"
@@ -161,6 +144,7 @@ Item {
horizontalAlignment: Text.AlignRight
font.pixelSize: Style.topToolbarSmallFontSize
font.capitalization: Font.AllUppercase
+ visible: rotationMain.visible
MouseArea {
anchors.fill: parent
onClicked: parent.showAddress = !parent.showAddress
@@ -174,6 +158,10 @@ Item {
font.pixelSize: Style.topToolbarLargeFontSize
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
+ MouseArea {
+ anchors.fill: parent
+ onClicked: clickBait.activate(logWindow)
+ }
}
Timer {
@@ -184,8 +172,10 @@ Item {
}
Text {
+ id: utcGmt
text: "UTC/GMT"
color: "white"
+ visible: dateLabel.visible
anchors.left: timeLabel.right
anchors.leftMargin: 16
anchors.bottom: parent.bottom
@@ -203,6 +193,7 @@ Item {
}
Image {
+ id: topbar
anchors.bottom: parent.bottom
anchors.bottomMargin: -18
source: pathPrefix + "Toolbar/topbar_all.png"
@@ -233,4 +224,6 @@ Item {
deactivate();
}
}
+
+ Component.onCompleted: clickBait.activate(sensorList)
}
diff --git a/tradeshow/iot-sensortag/resources/base/main.qml b/tradeshow/iot-sensortag/resources/base/main.qml
index 6d492d5..05b0f21 100644
--- a/tradeshow/iot-sensortag/resources/base/main.qml
+++ b/tradeshow/iot-sensortag/resources/base/main.qml
@@ -48,22 +48,35 @@
**
****************************************************************************/
import QtQuick 2.6
+import QtQuick.Controls 1.4
import QtQuick.Window 2.0
import SensorTag.DataProvider 1.0
import SensorTag.SeriesStorage 1.0
+import Style 1.0
-Window {
+ApplicationWindow {
id: mainWindow
+ property var singleSensorSource : null
property alias contentFile: contentLoader.source
- property DataProviderPool dataProviderPool
+ property DataProviderPool localProviderPool
+ property DataProviderPool remoteProviderPool
property SeriesStorage seriesStorage
property real globalBlinkOpacity: 1.0
property string addresses : ""
+ property bool localSelected : true
+ property var loggingOutput : null
+
+ function getCurrentPool() {
+ if (localSelected)
+ return localProviderPool
+ else
+ return remoteProviderPool
+ }
// Size defaults to the small display
- width: 1920
- height: 1080
+ width: Style.width
+ height: Style.height
visible: true
color: "black"
@@ -74,13 +87,11 @@ Window {
Loader {
id: contentLoader
-
+ visible: true
anchors.fill: parent
anchors.centerIn: parent
}
-
-
function startBlink() {
flash.blinkers++;
}
diff --git a/tradeshow/iot-sensortag/resources/large/MainLarge.qml b/tradeshow/iot-sensortag/resources/large/MainLarge.qml
deleted file mode 100644
index d855c0e..0000000
--- a/tradeshow/iot-sensortag/resources/large/MainLarge.qml
+++ /dev/null
@@ -1,177 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the examples of Qt for Device Creation.
-**
-** $QT_BEGIN_LICENSE:BSD$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** BSD License Usage
-** Alternatively, 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.6
-import SensorTag.DataProvider 1.0
-import "../base"
-
-Item {
- id: main
-
- width: 1920
- height: 1080
-
- Component.onCompleted: {
- dataProviderPool.startScanning()
- }
-
- Connections {
- target: dataProviderPool
- onScanFinished: {
- ambientTemp.sensor = dataProviderPool.getProvider(SensorTagData.AmbientTemperature);
- objectTemp.sensor = dataProviderPool.getProvider(SensorTagData.ObjectTemperature);
- humidity.sensor = dataProviderPool.getProvider(SensorTagData.Humidity);
- //airPressure.sensor = dataProviderPool.getProvider(SensorTagData.AirPressure);
- light.sensor = dataProviderPool.getProvider(SensorTagData.Light);
- magnetometer.sensor = dataProviderPool.getProvider(SensorTagData.Magnetometer);
- rotation.sensor = dataProviderPool.getProvider(SensorTagData.Rotation);
- }
- }
-
- Column {
- id: leftPane
-
- property int indicatorHeight: height / 4 - spacing
-
- anchors.left: parent.left
- anchors.leftMargin: 8
- anchors.top: main.top
- anchors.bottom: main.bottom
- width: 360
- spacing: 16
-
- TemperatureChart {
- id: ambientTemp
-
- width: leftPane.width
- height: leftPane.indicatorHeight
- }
-
- ObjectTemperatureChart {
- id: objectTemp
-
- width: leftPane.width
- height: leftPane.indicatorHeight
- }
-
- HumidityChart {
- id: humidity
-
- width: leftPane.width
- height: leftPane.indicatorHeight
- }
-
-// AirPressureChart {
-// id: airPressure
-
-// width: leftPane.width
-// height: leftPane.indicatorHeight
-// }
- }
-
- Column {
- id: rightPane
-
- anchors.right: parent.right
- anchors.rightMargin: 8
- anchors.top: main.top
- anchors.bottom: main.bottom
- width: 360
- spacing: 16
-
- LightChart {
- id: light
-
- width: rightPane.width
- height: leftPane.height / 4
- }
-
- MagnetometerChart {
- id: magnetometer
-
- width: rightPane.width
- height: leftPane.height / 4
- }
-
- GyroPage {
- id: rotation
-
- width: rightPane.width
- height: leftPane.height / 4
- onClicked: mainContainer.source = "../base/GyroPage.qml"
- }
- }
-
- Loader {
- id: mainContainer
-
- anchors.top: topToolbar.bottom
- anchors.left: leftPane.right
- anchors.leftMargin: 32
- anchors.right: rightPane.left
- anchors.rightMargin: 32
- anchors.bottom: bottomToolbar.top
- }
-
- TopToolbar {
- id: topToolbar
-
- anchors.left: leftPane.right
- anchors.right: rightPane.left
- }
-
- BottomToolbar {
- id: bottomToolbar
-
- anchors.left: leftPane.right
- anchors.right: rightPane.left
- anchors.bottom: main.bottom
- height: 30
- }
-}
diff --git a/tradeshow/iot-sensortag/resources/large/images/AirPressure/AirPre_base_gauge.png b/tradeshow/iot-sensortag/resources/large/images/AirPressure/AirPre_base_gauge.png
deleted file mode 100644
index 30a24b8..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/AirPressure/AirPre_base_gauge.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/AmbientTemperature/ambTemp_display_amb.png b/tradeshow/iot-sensortag/resources/large/images/AmbientTemperature/ambTemp_display_amb.png
deleted file mode 100644
index 5a4bcf0..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/AmbientTemperature/ambTemp_display_amb.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/General/icon_sensor.png b/tradeshow/iot-sensortag/resources/large/images/General/icon_sensor.png
deleted file mode 100644
index 8157ef7..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/General/icon_sensor.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/General/separator.png b/tradeshow/iot-sensortag/resources/large/images/General/separator.png
deleted file mode 100644
index 4dae4f8..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/General/separator.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_base_gauge.png b/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_base_gauge.png
deleted file mode 100644
index 0d0331c..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_base_gauge.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_max_hum.png b/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_max_hum.png
deleted file mode 100644
index 381c4dd..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_max_hum.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_min_hum.png b/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_min_hum.png
deleted file mode 100644
index c3f9442..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/Humidity/humidity_min_hum.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/Light/light_base_gauge.png b/tradeshow/iot-sensortag/resources/large/images/Light/light_base_gauge.png
deleted file mode 100644
index 6a68729..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/Light/light_base_gauge.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/Light/light_brightness.png b/tradeshow/iot-sensortag/resources/large/images/Light/light_brightness.png
deleted file mode 100644
index 45b1e2b..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/Light/light_brightness.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/ObjectTemperature/objTemp_base_circle.png b/tradeshow/iot-sensortag/resources/large/images/ObjectTemperature/objTemp_base_circle.png
deleted file mode 100644
index a506d2a..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/ObjectTemperature/objTemp_base_circle.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/large/images/ObjectTemperature/objTemp_display_obj.png b/tradeshow/iot-sensortag/resources/large/images/ObjectTemperature/objTemp_display_obj.png
deleted file mode 100644
index bc320ac..0000000
--- a/tradeshow/iot-sensortag/resources/large/images/ObjectTemperature/objTemp_display_obj.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/MainMedium.qml b/tradeshow/iot-sensortag/resources/medium/MainMedium.qml
deleted file mode 100644
index d855c0e..0000000
--- a/tradeshow/iot-sensortag/resources/medium/MainMedium.qml
+++ /dev/null
@@ -1,177 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the examples of Qt for Device Creation.
-**
-** $QT_BEGIN_LICENSE:BSD$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** BSD License Usage
-** Alternatively, 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.6
-import SensorTag.DataProvider 1.0
-import "../base"
-
-Item {
- id: main
-
- width: 1920
- height: 1080
-
- Component.onCompleted: {
- dataProviderPool.startScanning()
- }
-
- Connections {
- target: dataProviderPool
- onScanFinished: {
- ambientTemp.sensor = dataProviderPool.getProvider(SensorTagData.AmbientTemperature);
- objectTemp.sensor = dataProviderPool.getProvider(SensorTagData.ObjectTemperature);
- humidity.sensor = dataProviderPool.getProvider(SensorTagData.Humidity);
- //airPressure.sensor = dataProviderPool.getProvider(SensorTagData.AirPressure);
- light.sensor = dataProviderPool.getProvider(SensorTagData.Light);
- magnetometer.sensor = dataProviderPool.getProvider(SensorTagData.Magnetometer);
- rotation.sensor = dataProviderPool.getProvider(SensorTagData.Rotation);
- }
- }
-
- Column {
- id: leftPane
-
- property int indicatorHeight: height / 4 - spacing
-
- anchors.left: parent.left
- anchors.leftMargin: 8
- anchors.top: main.top
- anchors.bottom: main.bottom
- width: 360
- spacing: 16
-
- TemperatureChart {
- id: ambientTemp
-
- width: leftPane.width
- height: leftPane.indicatorHeight
- }
-
- ObjectTemperatureChart {
- id: objectTemp
-
- width: leftPane.width
- height: leftPane.indicatorHeight
- }
-
- HumidityChart {
- id: humidity
-
- width: leftPane.width
- height: leftPane.indicatorHeight
- }
-
-// AirPressureChart {
-// id: airPressure
-
-// width: leftPane.width
-// height: leftPane.indicatorHeight
-// }
- }
-
- Column {
- id: rightPane
-
- anchors.right: parent.right
- anchors.rightMargin: 8
- anchors.top: main.top
- anchors.bottom: main.bottom
- width: 360
- spacing: 16
-
- LightChart {
- id: light
-
- width: rightPane.width
- height: leftPane.height / 4
- }
-
- MagnetometerChart {
- id: magnetometer
-
- width: rightPane.width
- height: leftPane.height / 4
- }
-
- GyroPage {
- id: rotation
-
- width: rightPane.width
- height: leftPane.height / 4
- onClicked: mainContainer.source = "../base/GyroPage.qml"
- }
- }
-
- Loader {
- id: mainContainer
-
- anchors.top: topToolbar.bottom
- anchors.left: leftPane.right
- anchors.leftMargin: 32
- anchors.right: rightPane.left
- anchors.rightMargin: 32
- anchors.bottom: bottomToolbar.top
- }
-
- TopToolbar {
- id: topToolbar
-
- anchors.left: leftPane.right
- anchors.right: rightPane.left
- }
-
- BottomToolbar {
- id: bottomToolbar
-
- anchors.left: leftPane.right
- anchors.right: rightPane.left
- anchors.bottom: main.bottom
- height: 30
- }
-}
diff --git a/tradeshow/iot-sensortag/resources/medium/images/AirPressure/AirPre_base_gauge.png b/tradeshow/iot-sensortag/resources/medium/images/AirPressure/AirPre_base_gauge.png
deleted file mode 100644
index 2f1d658..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/AirPressure/AirPre_base_gauge.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/AmbientTemperature/ambTemp_display_amb.png b/tradeshow/iot-sensortag/resources/medium/images/AmbientTemperature/ambTemp_display_amb.png
deleted file mode 100644
index cad440a..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/AmbientTemperature/ambTemp_display_amb.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/General/icon_sensor.png b/tradeshow/iot-sensortag/resources/medium/images/General/icon_sensor.png
deleted file mode 100644
index 49cc090..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/General/icon_sensor.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/General/separator.png b/tradeshow/iot-sensortag/resources/medium/images/General/separator.png
deleted file mode 100644
index dbd25a4..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/General/separator.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_base_gauge.png b/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_base_gauge.png
deleted file mode 100644
index 494a0e6..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_base_gauge.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_max_hum.png b/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_max_hum.png
deleted file mode 100644
index c4a7d7b..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_max_hum.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_min_hum.png b/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_min_hum.png
deleted file mode 100644
index 75b83d1..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/Humidity/humidity_min_hum.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/Light/light_base_gauge.png b/tradeshow/iot-sensortag/resources/medium/images/Light/light_base_gauge.png
deleted file mode 100644
index 9c66fbb..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/Light/light_base_gauge.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/Light/light_brightness.png b/tradeshow/iot-sensortag/resources/medium/images/Light/light_brightness.png
deleted file mode 100644
index fc8e82a..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/Light/light_brightness.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/ObjectTemperature/objTemp_base_circle.png b/tradeshow/iot-sensortag/resources/medium/images/ObjectTemperature/objTemp_base_circle.png
deleted file mode 100644
index 86c966f..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/ObjectTemperature/objTemp_base_circle.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/medium/images/ObjectTemperature/objTemp_display_obj.png b/tradeshow/iot-sensortag/resources/medium/images/ObjectTemperature/objTemp_display_obj.png
deleted file mode 100644
index 77d53f3..0000000
--- a/tradeshow/iot-sensortag/resources/medium/images/ObjectTemperature/objTemp_display_obj.png
+++ /dev/null
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/small/MainSmall.qml b/tradeshow/iot-sensortag/resources/small/MainSmall.qml
index 83835e3..7528b6b 100644
--- a/tradeshow/iot-sensortag/resources/small/MainSmall.qml
+++ b/tradeshow/iot-sensortag/resources/small/MainSmall.qml
@@ -55,30 +55,20 @@ Item {
id: main
function startRescan(sensor) {
- if (sensor.state === SensorTagData.NotFound)
- dataProviderPool.startScanning();
- else if (sensor.state === SensorTagData.Scanning)
- dataProviderPool.disconnectProvider(sensor.providerId)
- else if (sensor.state !== SensorTagData.Connected)
- sensor.startServiceScan();
+ // ### Only do this magic when a local device is connected
+// if (sensor.state === SensorTagData.NotFound)
+// dataProviderPool.startScanning();
+// else if (sensor.state === SensorTagData.Scanning)
+// dataProviderPool.disconnectProvider(sensor.providerId)
+// else if (sensor.state !== SensorTagData.Connected)
+// sensor.startServiceScan();
}
anchors.fill: parent
Component.onCompleted: {
- dataProviderPool.startScanning();
-
- // UI gets information about the intended setup of the
- // sensor even though they have not been really discovered yet
- ambientTemp.sensor = dataProviderPool.getProvider(SensorTagData.AmbientTemperature);
- objectTemp.sensor = dataProviderPool.getProvider(SensorTagData.ObjectTemperature);
- humidity.sensor = dataProviderPool.getProvider(SensorTagData.Humidity);
- airPressure.sensor = dataProviderPool.getProvider(SensorTagData.AirPressure);
- light.sensor = dataProviderPool.getProvider(SensorTagData.Light);
- magnetometer.sensor = dataProviderPool.getProvider(SensorTagData.Magnetometer);
- rotation.sensor = dataProviderPool.getProvider(SensorTagData.Rotation);
- accelometer.sensor = dataProviderPool.getProvider(SensorTagData.Accelometer);
- rotationMain.sensor = dataProviderPool.getProvider(SensorTagData.Rotation);
+ localProviderPool.startScanning();
+ remoteProviderPool.startScanning();
}
Column {
@@ -172,6 +162,7 @@ Item {
RotationPage {
id: rotationMain
+ visible: main.width > (leftPane.width + rightPane.width + 800)
anchors.top: topToolbar.bottom
anchors.left: leftPane.right
anchors.leftMargin: 32
diff --git a/tradeshow/iot-sensortag/resources/small/StyleSmall.qml b/tradeshow/iot-sensortag/resources/small/StyleSmall.qml
index e4d439d..aa19262 100644
--- a/tradeshow/iot-sensortag/resources/small/StyleSmall.qml
+++ b/tradeshow/iot-sensortag/resources/small/StyleSmall.qml
@@ -56,4 +56,9 @@ QtObject {
property int topToolbarSmallFontSize: 20
property int topToolbarLargeFontSize: 62
+
+ property int height: 1080
+ property int width: 1920
+
+ property string uiStyle: "small" // "watch",...
}
diff --git a/tradeshow/iot-sensortag/resources/watch/MainWatch.qml b/tradeshow/iot-sensortag/resources/watch/MainWatch.qml
new file mode 100644
index 0000000..5a500a2
--- /dev/null
+++ b/tradeshow/iot-sensortag/resources/watch/MainWatch.qml
@@ -0,0 +1,301 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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.7
+import QtQuick.Layouts 1.3
+import QtQuick.Controls 2.2
+import SensorTag.DataProvider 1.0
+import "../base"
+
+Item {
+ id: main
+
+ function startRescan(sensor) {
+ // ### Only do this magic when a local device is connected
+// if (sensor.state === SensorTagData.NotFound)
+// dataProviderPool.startScanning();
+// else if (sensor.state === SensorTagData.Scanning)
+// dataProviderPool.disconnectProvider(sensor.providerId)
+// else if (sensor.state !== SensorTagData.Connected)
+// sensor.startServiceScan();
+ }
+
+ anchors.fill: parent
+
+ Component.onCompleted: {
+ localProviderPool.startScanning();
+ remoteProviderPool.startScanning();
+ }
+
+ property int titleFontSize: 22
+
+ SwipeView {
+ id: swipePane
+ currentIndex: 5
+ anchors.left: main.left
+ anchors.leftMargin: 8
+ anchors.top: topToolbar.bottom
+ anchors.bottom: main.bottom
+ width: main.width
+ height: main.height
+
+ Item {
+ RotationPage {
+ scale: 0.35
+ width: swipePane.width
+ height: swipePane.height
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("Ambient Temperature")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+ TemperatureChart {
+ id: ambientTemp
+ width: swipePane.width
+ height: swipePane.height
+ onClicked: main.startRescan(sensor)
+ scale: 0.85
+ title: ""
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("Object Temperature")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+ ObjectTemperatureChart {
+ id: objectTemp
+ anchors.fill: parent
+ anchors.leftMargin: -40
+ onClicked: main.startRescan(sensor)
+ scale: 0.8
+ title: ""
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("Humidity")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+ HumidityChart {
+ id: humidity
+ onClicked: main.startRescan(sensor)
+ title: ""
+ scale: 0.80
+ anchors.fill: parent
+ anchors.leftMargin: -40
+ anchors.topMargin: 17
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("Altitude")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+ AltitudeChart {
+ id: airPressure
+ width: swipePane.width
+ height: swipePane.height
+ onClicked: main.startRescan(sensor)
+ title: ""
+ scale: 0.80
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("Light Intensity")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+ LightChart {
+ id: light
+ height: swipePane.height
+ width: swipePane.width*1.1
+ scale: 0.70
+ onClicked: main.startRescan(sensor)
+ title: ""
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("Magnetometer")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+ MagnetometerChart {
+ id: magnetometer
+ width: swipePane.width
+ height: swipePane.height
+ onClicked: main.startRescan(sensor)
+ title: ""
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("Gyroscope")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+ GyroChart {
+ id: rotation
+ width: swipePane.width
+ height: swipePane.height
+ onClicked: main.startRescan(sensor)
+ title: ""
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("Accelometer")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+ AccelChart {
+ id: accelometer
+ width: swipePane.width
+ height: swipePane.height
+ onClicked: main.startRescan(sensor)
+ title: ""
+ }
+ }
+
+ Item {
+ Text {
+ text: qsTr("About")
+ anchors.fill: parent
+ color: "white"
+ font.pixelSize: titleFontSize
+ }
+
+ Rectangle {
+ id: aboutView
+ width: swipePane.width
+ height: swipePane.height
+ color: "transparent"
+
+ ColumnLayout {
+ id: aboutLayout
+ width: parent.width * 0.8
+ height: parent.width * 0.8
+ anchors.centerIn: parent
+
+ Image {
+ source: "images/BuiltWithQt.png"
+ fillMode: Image.PreserveAspectFit
+ width: parent.width
+ Layout.maximumWidth: parent.width - 20
+ Layout.maximumHeight: 100
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ Text {
+ id: qtLinkLabel
+ color: "white"
+ text: "Visit us at http://qt.io"
+ anchors.horizontalCenter: parent.horizontalCenter
+ font.pixelSize: Style.indicatorTitleSize
+ }
+
+ Text {
+ color: "white"
+ text: "qt.io/demos/IoTScale"
+ anchors.horizontalCenter: parent.horizontalCenter
+ font.pixelSize: Style.indicatorTitleSize
+ }
+ }
+ }
+ }
+
+ }
+
+ TopToolbar {
+ id: topToolbar
+ anchors.top: main.top
+ anchors.left: main.left
+ anchors.right: main.right
+ height: 48
+ topbar.visible: false
+ date.visible: false
+ Image {
+ id: quit
+ source: "images/Toolbar/exit_button.png"
+ fillMode: Image.PreserveAspectFit
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.margins: 5
+ scale: 0.7
+ MouseArea {
+ anchors.fill: parent
+ onClicked: Qt.quit()
+ }
+ }
+ }
+}
diff --git a/tradeshow/iot-sensortag/resources/medium/StyleMedium.qml b/tradeshow/iot-sensortag/resources/watch/StyleWatch.qml
index e4d439d..857b0f1 100644
--- a/tradeshow/iot-sensortag/resources/medium/StyleMedium.qml
+++ b/tradeshow/iot-sensortag/resources/watch/StyleWatch.qml
@@ -52,8 +52,13 @@ import QtQuick 2.0
QtObject {
property int indicatorTitleFontSize: 22
- property int indicatorTitleSize: 41
+ property int indicatorTitleSize: 20
- property int topToolbarSmallFontSize: 20
- property int topToolbarLargeFontSize: 62
+ property int topToolbarSmallFontSize: 10
+ property int topToolbarLargeFontSize: 30
+
+ property int height: 320
+ property int width: 320
+
+ property string uiStyle: "watch"
}
diff --git a/tradeshow/iot-sensortag/resources/watch/images/BuiltWithQt.png b/tradeshow/iot-sensortag/resources/watch/images/BuiltWithQt.png
new file mode 100644
index 0000000..7e56de7
--- /dev/null
+++ b/tradeshow/iot-sensortag/resources/watch/images/BuiltWithQt.png
Binary files differ
diff --git a/tradeshow/iot-sensortag/resources/watch/images/Toolbar/exit_button.png b/tradeshow/iot-sensortag/resources/watch/images/Toolbar/exit_button.png
new file mode 100644
index 0000000..06f2ebe
--- /dev/null
+++ b/tradeshow/iot-sensortag/resources/watch/images/Toolbar/exit_button.png
Binary files differ
diff --git a/tradeshow/iot-sensortag/sensortagdataprovider.cpp b/tradeshow/iot-sensortag/sensortagdataprovider.cpp
index f5defa3..409b95a 100644
--- a/tradeshow/iot-sensortag/sensortagdataprovider.cpp
+++ b/tradeshow/iot-sensortag/sensortagdataprovider.cpp
@@ -47,6 +47,7 @@
** $QT_END_LICENSE$
**
****************************************************************************/
+
#include "sensortagdataprovider.h"
#include <QtCore/QtMath>
@@ -72,36 +73,35 @@ SensorTagDataProvider::SensorTagDataProvider(QObject *parent)
: QObject(parent)
, pressureAtZeroAltitude(AIR_PRESSURE_P0)
{
-
}
-SensorTagDataProvider::SensorTagDataProvider(QString id, QObject* parent)
- : QObject(parent),
- humidity(0),
- irAmbientTemperature(0),
- irObjectTemperature(0),
- lightIntensityLux(0),
- barometerCelsiusTemperature(0),
- barometerTemperatureAverage(25),
- barometerHPa(0),
- gyroscopeX_degPerSec(0),
- gyroscopeY_degPerSec(0),
- gyroscopeZ_degPerSec(0),
- accelometerX(0),
- accelometerY(0),
- accelometerZ(0),
+SensorTagDataProvider::SensorTagDataProvider(QString id, QObject *parent)
+ : QObject(parent)
+ , humidity(0)
+ , irAmbientTemperature(0)
+ , irObjectTemperature(0)
+ , lightIntensityLux(0)
+ , barometerCelsiusTemperature(0)
+ , barometerTemperatureAverage(25)
+ , barometerHPa(0)
+ , gyroscopeX_degPerSec(0)
+ , gyroscopeY_degPerSec(0)
+ , gyroscopeZ_degPerSec(0)
+ , accelometerX(0)
+ , accelometerY(0)
+ , accelometerZ(0)
/* Object is not "walking in the air" so have one axis at 1G */
- magnetometerMicroT_xAxis(-1),
- magnetometerMicroT_yAxis(0),
- magnetometerMicroT_zAxis(0),
- rotation_x(0),
- rotation_y(0),
- rotation_z(0),
- intervalRotation(DEFAULT_REFRESH_INTERVAL_MS),
- altitude(0),
- m_tagType(AmbientTemperature | ObjectTemperature | Humidity | AirPressure | Light | Magnetometer | Rotation | Accelometer | Altitude),
- m_id(id),
- m_state(NotFound)
+ , magnetometerMicroT_xAxis(-1)
+ , magnetometerMicroT_yAxis(0)
+ , magnetometerMicroT_zAxis(0)
+ , rotation_x(0)
+ , rotation_y(0)
+ , rotation_z(0)
+ , intervalRotation(DEFAULT_REFRESH_INTERVAL_MS)
+ , altitude(0)
+ , m_tagType(AmbientTemperature | ObjectTemperature | Humidity | AirPressure | Light | Magnetometer | Rotation | Accelometer | Altitude)
+ , m_id(id)
+ , m_state(NotFound)
{
}
diff --git a/tradeshow/iot-sensortag/sensortagdataprovider.h b/tradeshow/iot-sensortag/sensortagdataprovider.h
index 523bd93..f3a6dcb 100644
--- a/tradeshow/iot-sensortag/sensortagdataprovider.h
+++ b/tradeshow/iot-sensortag/sensortagdataprovider.h
@@ -49,6 +49,7 @@
****************************************************************************/
#ifndef SENSORTAGDATAPROVIDER_H
#define SENSORTAGDATAPROVIDER_H
+
#include <QObject>
#include <QString>
#include <QColor>
@@ -100,13 +101,19 @@ public:
Altitude = 1 << 8
};
static const int tagTypeCount = 9;
- enum ProviderState {NotFound = 0, Disconnected, Scanning, Connected, Error};
+ enum ProviderState {
+ NotFound = 0,
+ Disconnected,
+ Scanning,
+ Connected,
+ Error
+ };
explicit SensorTagDataProvider(QObject *parent = 0);
SensorTagDataProvider(QString id, QObject *parent = 0);
- virtual bool startDataFetching() {return false;}
- virtual void endDataFetching() {}
+ Q_INVOKABLE virtual bool startDataFetching() {return false;}
+ Q_INVOKABLE virtual void endDataFetching() {}
QString getRelativeHumidityString() const;
virtual double getRelativeHumidity() const;
virtual double getInfraredAmbientTemperature() const;
@@ -176,7 +183,6 @@ protected:
double barometerCelsiusTemperature;
double barometerTemperatureAverage;
double barometerHPa;
- //double temperatureAverage;
float gyroscopeX_degPerSec;
float gyroscopeY_degPerSec;
float gyroscopeZ_degPerSec;
diff --git a/tradeshow/iot-sensortag/sensortagdataproviderpool.cpp b/tradeshow/iot-sensortag/sensortagdataproviderpool.cpp
index 987e8b3..3b66d90 100644
--- a/tradeshow/iot-sensortag/sensortagdataproviderpool.cpp
+++ b/tradeshow/iot-sensortag/sensortagdataproviderpool.cpp
@@ -58,22 +58,23 @@ Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos)
SensorTagDataProviderPool::SensorTagDataProviderPool(QObject *parent)
: SensorTagDataProviderPool("SensorTag", parent)
{
-
}
-SensorTagDataProviderPool::SensorTagDataProviderPool(QString poolName, QObject* parent)
+SensorTagDataProviderPool::SensorTagDataProviderPool(QString poolName, QObject *parent)
: DataProviderPool(poolName, parent)
{
m_discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
- connect(m_discoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)),
- this, SLOT(btDeviceFound(const QBluetoothDeviceInfo&)));
- connect(m_discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)),
- this, SLOT(deviceScanError(QBluetoothDeviceDiscoveryAgent::Error)));
- connect(m_discoveryAgent, SIGNAL(finished()), this, SLOT(deviceDiscoveryFinished()));
+ connect(m_discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
+ this, &SensorTagDataProviderPool::btDeviceFound);
+ connect(m_discoveryAgent, QOverload<QBluetoothDeviceDiscoveryAgent::Error>::of(&QBluetoothDeviceDiscoveryAgent::error),
+ this, &SensorTagDataProviderPool::deviceScanError);
+ connect(m_discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
+ this, &SensorTagDataProviderPool::deviceDiscoveryFinished);
}
void SensorTagDataProviderPool::startScanning()
{
+ m_discoveryAgent->setLowEnergyDiscoveryTimeout(0);
m_discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
if (m_discoveryAgent->isActive()) {
@@ -81,7 +82,7 @@ void SensorTagDataProviderPool::startScanning()
}
}
-void SensorTagDataProviderPool::disconnectProvider(QString id)
+void SensorTagDataProviderPool::disconnectProvider(const QString &id)
{
SensorTagDataProvider *p = findProvider(id);
if (BluetoothDataProvider *btp = qobject_cast<BluetoothDataProvider*>(findProvider(id)))
@@ -90,21 +91,6 @@ void SensorTagDataProviderPool::disconnectProvider(QString id)
p->setState(SensorTagDataProvider::Disconnected);
}
-void SensorTagDataProviderPool::setMacFilterList(const QStringList &addressList)
-{
- m_macFilters = addressList;
-}
-
-QStringList SensorTagDataProviderPool::macFilters() const
-{
- return m_macFilters;
-}
-
-void SensorTagDataProviderPool::setnameFilteList(const QStringList &nameList)
-{
- m_nameFilters = nameList;
-}
-
SensorTagDataProvider *SensorTagDataProviderPool::providerForCloud() const
{
return m_providerForCloud;
@@ -136,40 +122,27 @@ void SensorTagDataProviderPool::finishScanning()
void SensorTagDataProviderPool::btDeviceFound(const QBluetoothDeviceInfo &info)
{
if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
- bool filtered = m_macFilters.length() || m_nameFilters.length();
- bool found = filtered ? false : true;
-
- if (m_macFilters.length() && m_macFilters.contains(info.address().toString()))
- found = true;
- else if (m_nameFilters.length() && m_nameFilters.contains(info.name()))
- found = true;
-
- if (found) {
- BluetoothDataProvider *dataProvider = static_cast<BluetoothDataProvider *>(findProvider(info.address().toString()));
- if (!dataProvider) {
- qCDebug(boot2QtDemos) << "Found a new Sensor tag. Name:" << info.name() << ", addr:" << info.address().toString() ;
- dataProvider = new BluetoothDataProvider(info.address().toString(), this);
- m_dataProviders.append(dataProvider);
- emit dataProvidersChanged();
- }
- if (!dataProvider->device()) {
- qCDebug(boot2QtDemos) << "Attach BluetoothDevice info for an existing Sensor Tag:" << info.name();
- BluetoothDevice *d = new BluetoothDevice(info);
- dataProvider->bindToDevice(d);
- connect(dataProvider, &SensorTagDataProvider::stateChanged, this, &SensorTagDataProviderPool::handleStateChange);
- dataProvider->startDataFetching();
- }
- else if (dataProvider->state() != SensorTagDataProvider::Connected) {
- qCDebug(boot2QtDemos) << "Start service scan for already attached Sensor Tag" << dataProvider->id();
- dataProvider->startServiceScan();
- }
+ // all tags by TI have this naming convention
+ if (info.name().contains(QLatin1String("SensorTag")) && !m_knownDevices.contains(info)) {
+ qCDebug(boot2QtDemos) << "Found a new device:" << info.name();
+ m_knownDevices.append(info);
+ BluetoothDataProvider *p = new BluetoothDataProvider(info, this);
+ m_dataProviders.push_back(p);
+ connect(p, &SensorTagDataProvider::stateChanged,
+ this, &SensorTagDataProviderPool::handleStateChange);
+
+ emit dataProvidersChanged();
+
+ // Set initial state to Scanning for UI to be
+ // able to show "Connecting.." information
+ p->setState(SensorTagDataProvider::Scanning);
}
}
}
void SensorTagDataProviderPool::handleStateChange()
{
- SensorTagDataProvider *provider = static_cast<SensorTagDataProvider*>(sender());
+ SensorTagDataProvider *provider = static_cast<SensorTagDataProvider *>(sender());
switch (provider->state()) {
case SensorTagDataProvider::Disconnected:
@@ -200,7 +173,7 @@ void SensorTagDataProviderPool::deviceScanError(QBluetoothDeviceDiscoveryAgent::
emit scanFinished();
}
-SensorTagDataProvider* SensorTagDataProviderPool::findProvider(QString id) const
+SensorTagDataProvider *SensorTagDataProviderPool::findProvider(QString id) const
{
for (SensorTagDataProvider *p : m_dataProviders) {
if (id == p->id())
diff --git a/tradeshow/iot-sensortag/sensortagdataproviderpool.h b/tradeshow/iot-sensortag/sensortagdataproviderpool.h
index fe8b30f..6353d24 100644
--- a/tradeshow/iot-sensortag/sensortagdataproviderpool.h
+++ b/tradeshow/iot-sensortag/sensortagdataproviderpool.h
@@ -66,23 +66,15 @@ public:
explicit SensorTagDataProviderPool(QObject *parent = 0);
void startScanning() override;
- void disconnectProvider(QString id) override;
+ void disconnectProvider(const QString &id) override;
- // setMacFilterList takes presence over name filter
- Q_INVOKABLE void setMacFilterList(const QStringList& addressList);
- QStringList macFilters() const;
- Q_INVOKABLE void setnameFilteList(const QStringList& nameList);
-
- SensorTagDataProvider* providerForCloud() const override;
+ SensorTagDataProvider *providerForCloud() const override;
protected:
SensorTagDataProviderPool(QString poolName, QObject *parent = 0);
virtual void finishScanning();
void updateProviderForCloud();
- QStringList m_macFilters;
- QStringList m_nameFilters;
-
private slots:
void deviceDiscoveryFinished();
void btDeviceFound(const QBluetoothDeviceInfo &info);
@@ -93,6 +85,7 @@ private:
SensorTagDataProvider *findProvider(QString id) const;
QBluetoothDeviceDiscoveryAgent *m_discoveryAgent;
+ QList<QBluetoothDeviceInfo> m_knownDevices;
SensorTagDataProvider *m_providerForCloud;
};
diff --git a/tradeshow/iot-sensortag/seriesstorage.cpp b/tradeshow/iot-sensortag/seriesstorage.cpp
index 556499c..5c032d8 100644
--- a/tradeshow/iot-sensortag/seriesstorage.cpp
+++ b/tradeshow/iot-sensortag/seriesstorage.cpp
@@ -47,6 +47,7 @@
** $QT_END_LICENSE$
**
****************************************************************************/
+
#include "seriesstorage.h"
Q_DECLARE_METATYPE(QAbstractSeries *)
@@ -70,7 +71,7 @@ void SeriesStorage::setDataProviderPool(DataProviderPool *pool)
m_providerPool = pool;
if (!m_providerPool)
return;
- connect(m_providerPool, &DataProviderPool::dataProvidersChanged, this, &SeriesStorage::dataProviderPoolChanged);
+ connect(m_providerPool, &DataProviderPool::currentProviderIndexChanged, this, &SeriesStorage::dataProviderPoolChanged);
}
void SeriesStorage::setTemperatureSeries(QAbstractSeries *series)
@@ -94,20 +95,20 @@ void SeriesStorage::setMagnetoMeterSeries(QAbstractSeries *xSeries, QAbstractSer
void SeriesStorage::dataProviderPoolChanged()
{
- m_gyroProvider = m_providerPool->getProvider(SensorTagDataProvider::Rotation);
- if (m_gyroProvider) {
- connect(m_gyroProvider, &SensorTagDataProvider::rotationValuesChanged, this, &SeriesStorage::changeRotationSeries);
- }
-
- m_magnetoProvider = m_providerPool->getProvider(SensorTagDataProvider::Magnetometer);
- if (m_magnetoProvider) {
- connect(m_magnetoProvider, &SensorTagDataProvider::magnetometerMicroTChanged, this, &SeriesStorage::changeMagnetoSeries);
- }
-
- m_temperatureProvider = m_providerPool->getProvider(SensorTagDataProvider::AmbientTemperature);
- if (m_temperatureProvider) {
- connect(m_temperatureProvider, &SensorTagDataProvider::infraredAmbientTemperatureChanged, this, &SeriesStorage::changeTemperatureSeries);
- }
+ m_gyroProvider = m_providerPool->currentProvider();
+ if (m_gyroProvider)
+ connect(m_gyroProvider, &SensorTagDataProvider::rotationValuesChanged,
+ this, &SeriesStorage::changeRotationSeries, Qt::UniqueConnection);
+
+ m_magnetoProvider = m_providerPool->currentProvider();
+ if (m_magnetoProvider)
+ connect(m_magnetoProvider, &SensorTagDataProvider::magnetometerMicroTChanged,
+ this, &SeriesStorage::changeMagnetoSeries, Qt::UniqueConnection);
+
+ m_temperatureProvider = m_providerPool->currentProvider();
+ if (m_temperatureProvider)
+ connect(m_temperatureProvider, &SensorTagDataProvider::infraredAmbientTemperatureChanged,
+ this, &SeriesStorage::changeTemperatureSeries, Qt::UniqueConnection);
}
void SeriesStorage::changeRotationSeries()
@@ -146,11 +147,14 @@ void SeriesStorage::changeMagnetoSeries()
static int axisMin = 0;
static int axisMax = maxMagneticReadings;
- updatePoints(m_magnetoListX, magneticSeriesIndex, m_magnetoProvider->getMagnetometerMicroT_xAxis(), maxMagneticReadings);
+ updatePoints(m_magnetoListX, magneticSeriesIndex,
+ m_magnetoProvider->getMagnetometerMicroT_xAxis(), maxMagneticReadings);
m_magnetoX->replace(m_magnetoListX);
- updatePoints(m_magnetoListY, magneticSeriesIndex, m_magnetoProvider->getMagnetometerMicroT_yAxis(), maxMagneticReadings);
+ updatePoints(m_magnetoListY, magneticSeriesIndex,
+ m_magnetoProvider->getMagnetometerMicroT_yAxis(), maxMagneticReadings);
m_magnetoY->replace(m_magnetoListY);
- updatePoints(m_magnetoListZ, magneticSeriesIndex, m_magnetoProvider->getMagnetometerMicroT_zAxis(), maxMagneticReadings);
+ updatePoints(m_magnetoListZ, magneticSeriesIndex,
+ m_magnetoProvider->getMagnetometerMicroT_zAxis(), maxMagneticReadings);
m_magnetoZ->replace(m_magnetoListZ);
if (magneticSeriesIndex >= maxMagneticReadings) {
@@ -174,7 +178,7 @@ void SeriesStorage::changeTemperatureSeries()
static const int defaultAvgValue = 25;
m_temperatureList.removeLast();
- m_temperatureList.append(QPointF(temperatureSeriesIndex -1, m_temperatureProvider->getInfraredAmbientTemperature()));
+ m_temperatureList.append(QPointF(temperatureSeriesIndex - 1, m_temperatureProvider->getInfraredAmbientTemperature()));
m_temperatureList.append(QPointF(temperatureSeriesIndex, defaultAvgValue));
if (m_temperatureList.size() >= maxTemperatureReadings)
diff --git a/tradeshow/iot-sensortag/seriesstorage.h b/tradeshow/iot-sensortag/seriesstorage.h
index 94447c3..5bc40bb 100644
--- a/tradeshow/iot-sensortag/seriesstorage.h
+++ b/tradeshow/iot-sensortag/seriesstorage.h
@@ -63,7 +63,7 @@ class SeriesStorage : public QObject
public:
explicit SeriesStorage(QObject *parent = nullptr);
- void setDataProviderPool(DataProviderPool *pool);
+ Q_INVOKABLE void setDataProviderPool(DataProviderPool *pool);
Q_INVOKABLE void setTemperatureSeries(QAbstractSeries *series);
Q_INVOKABLE void setGyroSeries(QAbstractSeries *xSeries, QAbstractSeries *ySeries,
QAbstractSeries *zSeries);
diff --git a/tradeshow/iot-sensortag/uilarge.qrc b/tradeshow/iot-sensortag/uilarge.qrc
deleted file mode 100644
index 6885e21..0000000
--- a/tradeshow/iot-sensortag/uilarge.qrc
+++ /dev/null
@@ -1,17 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>resources/large/MainLarge.qml</file>
- <file>resources/large/StyleLarge.qml</file>
- <file>resources/large/images/General/icon_sensor.png</file>
- <file>resources/large/images/General/separator.png</file>
- <file>resources/large/images/AmbientTemperature/ambTemp_display_amb.png</file>
- <file>resources/large/images/Humidity/humidity_base_gauge.png</file>
- <file>resources/large/images/Humidity/humidity_max_hum.png</file>
- <file>resources/large/images/Humidity/humidity_min_hum.png</file>
- <file>resources/large/images/ObjectTemperature/objTemp_base_circle.png</file>
- <file>resources/large/images/ObjectTemperature/objTemp_display_obj.png</file>
- <file>resources/large/images/Light/light_base_gauge.png</file>
- <file>resources/large/images/Light/light_brightness.png</file>
- <file>resources/large/images/AirPressure/AirPre_base_gauge.png</file>
- </qresource>
-</RCC>
diff --git a/tradeshow/iot-sensortag/uimedium.qrc b/tradeshow/iot-sensortag/uimedium.qrc
deleted file mode 100644
index bb4324c..0000000
--- a/tradeshow/iot-sensortag/uimedium.qrc
+++ /dev/null
@@ -1,17 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>resources/medium/MainMedium.qml</file>
- <file>resources/medium/StyleMedium.qml</file>
- <file>resources/medium/images/AmbientTemperature/ambTemp_display_amb.png</file>
- <file>resources/medium/images/General/icon_sensor.png</file>
- <file>resources/medium/images/General/separator.png</file>
- <file>resources/medium/images/Humidity/humidity_max_hum.png</file>
- <file>resources/medium/images/Humidity/humidity_min_hum.png</file>
- <file>resources/medium/images/Humidity/humidity_base_gauge.png</file>
- <file>resources/medium/images/ObjectTemperature/objTemp_base_circle.png</file>
- <file>resources/medium/images/ObjectTemperature/objTemp_display_obj.png</file>
- <file>resources/medium/images/Light/light_base_gauge.png</file>
- <file>resources/medium/images/Light/light_brightness.png</file>
- <file>resources/medium/images/AirPressure/AirPre_base_gauge.png</file>
- </qresource>
-</RCC>
diff --git a/tradeshow/iot-sensortag/uiwatch.qrc b/tradeshow/iot-sensortag/uiwatch.qrc
new file mode 100644
index 0000000..5167663
--- /dev/null
+++ b/tradeshow/iot-sensortag/uiwatch.qrc
@@ -0,0 +1,35 @@
+<RCC>
+ <qresource prefix="/">
+ <file>resources/watch/MainWatch.qml</file>
+ <file>resources/watch/StyleWatch.qml</file>
+ <file>resources/watch/images/Toolbar/exit_button.png</file>
+ <file>resources/small/images/General/icon_sensor.png</file>
+ <file>resources/small/images/General/separator.png</file>
+ <file>resources/small/images/Light/light_base_gauge.png</file>
+ <file>resources/small/images/Light/light_brightness.png</file>
+ <file>resources/small/images/Toolbar/icon_topbar_sensor.png</file>
+ <file>resources/small/images/Toolbar/topbar_all.png</file>
+ <file>resources/small/images/Toolbar/icon_topbar_cloud.png</file>
+ <file>resources/small/images/Accelometer/inner_ring.png</file>
+ <file>resources/small/images/Accelometer/outer_ring.png</file>
+ <file>resources/small/images/AmbientTemperature/temp_ring.png</file>
+ <file>resources/small/images/AmbientTemperature/temp_sensor.png</file>
+ <file>resources/small/images/Toolbar/Cloud_button_change_active.png</file>
+ <file>resources/small/images/Toolbar/Cloud_icon_settings.png</file>
+ <file>resources/small/images/ObjectTemperature/objTemp_display_obj_blue.png</file>
+ <file>resources/small/images/ObjectTemperature/objTemp_display_obj_green.png</file>
+ <file>resources/small/images/ObjectTemperature/objTemp_display_obj_orange.png</file>
+ <file>resources/small/images/ObjectTemperature/objTemp_display_obj_red.png</file>
+ <file>resources/small/images/ObjectTemperature/objTemp_outer_inner_ring.png</file>
+ <file>resources/small/images/Humidity/Hum_combined_all.png</file>
+ <file>resources/small/images/Altitude/Height_bar.png</file>
+ <file>resources/small/images/Altitude/Altitude_base_gauge.png</file>
+ <file>resources/small/images/Altitude/Altitude_base_gauge_outer.png</file>
+ <file>resources/small/images/Gyro/gyro_center.png</file>
+ <file>resources/small/images/Gyro/gyro_ring1.png</file>
+ <file>resources/small/images/Gyro/gyro_ring2.png</file>
+ <file>resources/small/images/Gyro/gyro_ring3.png</file>
+ <file>resources/small/images/Gyro/gyro_outer.png</file>
+ <file>resources/watch/images/BuiltWithQt.png</file>
+ </qresource>
+</RCC>
diff --git a/wayland/democompositor/.gitignore b/wayland/democompositor/.gitignore
new file mode 100644
index 0000000..acd6488
--- /dev/null
+++ b/wayland/democompositor/.gitignore
@@ -0,0 +1,11 @@
+.obj/
+.moc/
+.rcc/
+*.sw?
+Makefile
+target_wrapper.sh
+
+Makefile.democompositor
+democompositor
+tests/applist/tst_applistmodel
+tests/apps/tst_apps
diff --git a/wayland/democompositor/apps/README b/wayland/democompositor/apps/README
new file mode 100644
index 0000000..2f6fd77
--- /dev/null
+++ b/wayland/democompositor/apps/README
@@ -0,0 +1,22 @@
+A directory holding a list of applications. There is one JSON file
+per launchable application. The content is inspired by the "Desktop
+Entry Specification" of freedesktop.org.
+
+Application files need to be installed into any of the paths that
+will be listed for QStandardPaths::DataLocation/apps. In the
+case of the democompositor this can be
+/usr/share/democompositor/apps.
+
+The content of the files must follow:
+
+Name Description Required JSON type
+Type Type of entry. Currently only Yes String
+ Application is supported.
+Version The version of the specification
+ addressed. Currently version 1 Yes Number
+Icon The icon to use for display Yes String
+Name A user displayable name Yes String
+Id Reverse-DNS as unique application Id Yes String
+Exec Application to execute to launch Yes String
+Path The directory to search for the app No String
+X-* Extensions to the specification No Any
diff --git a/tradeshow/iot-sensortag/resources/large/StyleLarge.qml b/wayland/democompositor/apps/appentry.cpp
index e4d439d..ce94cad 100644
--- a/tradeshow/iot-sensortag/resources/large/StyleLarge.qml
+++ b/wayland/democompositor/apps/appentry.cpp
@@ -47,13 +47,11 @@
** $QT_END_LICENSE$
**
****************************************************************************/
-pragma Singleton
-import QtQuick 2.0
-QtObject {
- property int indicatorTitleFontSize: 22
- property int indicatorTitleSize: 41
+#include "appentry.h"
- property int topToolbarSmallFontSize: 20
- property int topToolbarLargeFontSize: 62
+AppEntry AppEntry::empty()
+{
+ QString empty;
+ return AppEntry{empty, empty, empty, empty, empty, empty, QVariantMap()};
}
diff --git a/wayland/democompositor/apps/appentry.h b/wayland/democompositor/apps/appentry.h
new file mode 100644
index 0000000..bddc552
--- /dev/null
+++ b/wayland/democompositor/apps/appentry.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 once
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QVariantMap>
+
+/**
+ * A const representatation of an application entry. Members include
+ * executable name, path, icon and other information in the future.
+ */
+class AppEntry {
+ Q_GADGET
+ Q_PROPERTY(QString iconName MEMBER iconName CONSTANT)
+ Q_PROPERTY(QString appName MEMBER appName CONSTANT)
+ Q_PROPERTY(QString appId MEMBER appId CONSTANT)
+ Q_PROPERTY(QString executableName MEMBER executableName CONSTANT)
+ Q_PROPERTY(QString executablePath MEMBER executablePath CONSTANT)
+ Q_PROPERTY(QString sourceFileName MEMBER sourceFileName CONSTANT)
+ Q_PROPERTY(QVariantMap extensions MEMBER extensions CONSTANT)
+public:
+
+ QString iconName;
+ QString appName;
+ QString appId;
+ QString executableName;
+ QString executablePath;
+ QString sourceFileName;
+ QVariantMap extensions;
+
+ static AppEntry empty();
+};
+
+Q_DECLARE_METATYPE(AppEntry)
diff --git a/wayland/democompositor/apps/applistmodel.cpp b/wayland/democompositor/apps/applistmodel.cpp
new file mode 100644
index 0000000..dfcc378
--- /dev/null
+++ b/wayland/democompositor/apps/applistmodel.cpp
@@ -0,0 +1,221 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 "applistmodel.h"
+#include "appparser.h"
+#include "applog.h"
+
+#include <QtCore/QDirIterator>
+#include <QtCore/QFileSystemWatcher>
+
+static QHash<int, QByteArray> modelRoles()
+{
+ QHash<int, QByteArray> roles;
+ roles[AppListModel::App] = "appEntry";
+ roles[AppListModel::IconName] = "iconName";
+ roles[AppListModel::ApplicationName] = "applicationName";
+ roles[AppListModel::ApplicationId] = "applicationId";
+ roles[AppListModel::ExeuctableName] = "executableName";
+ roles[AppListModel::ExecutablePath] = "executablePath";
+ roles[AppListModel::SourceFileName] = "sourceFileName";
+ return roles;
+}
+
+QHash<int, QByteArray> AppListModel::m_roles = modelRoles();
+
+int AppListModel::rowCount(const QModelIndex& index) const
+{
+ if (index.isValid())
+ return 0;
+ return m_rows.count();
+}
+
+QVariant AppListModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || index.parent().isValid())
+ return QVariant();
+
+ auto entry = m_rows[index.row()];
+
+ switch (role) {
+ case App:
+ return QVariant::fromValue(entry);
+ case IconName:
+ return entry.iconName;
+ case ApplicationName:
+ return entry.appName;
+ case ApplicationId:
+ return entry.appId;
+ case ExeuctableName:
+ return entry.executableName;
+ case ExecutablePath:
+ return entry.executablePath;
+ case SourceFileName:
+ return entry.sourceFileName;
+ default:
+ qCWarning(apps) << "Unhandled role" << role;
+ return QVariant();
+ }
+}
+
+QHash<int, QByteArray> AppListModel::roleNames() const
+{
+ return m_roles;
+}
+
+/*!
+ * Returns true if the directory can be watched
+ *
+ * Parse all JSON application files from the given directory
+ * and monitor it for changes.
+ */
+bool AppListModel::addAndWatchDir(const QString& dirName)
+{
+ auto watcher = new QFileSystemWatcher(this);
+ connect(watcher, &QFileSystemWatcher::directoryChanged, this, &AppListModel::addDir);
+ auto res = watcher->addPath(dirName);
+ addDir(dirName);
+
+ qCDebug(apps) << "addAndWatchDir" << dirName << "result: " << res;
+ return res;
+}
+
+void AppListModel::addFile(const QString& fileName)
+{
+ beginResetModel();
+ doAddFile(fileName);
+ endResetModel();
+}
+
+void AppListModel::addDir(const QString& dirName)
+{
+ QDirIterator dirIt(dirName, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
+
+ // We want to deal with the corner case of some files being
+ // removed. One option would have been to keep a marker bit
+ // inside the AppEntry but that might leak too much of the
+ // implementation to the outside. Let's keep a list of file
+ // names that came from the directory and remove items from
+ // the list as we re-parse them. Once we are done emit the
+ // signals.
+ QVector<QString> deletionCandidates = entriesWithPrefix(dirName);
+
+ beginResetModel();
+ while (dirIt.hasNext()) {
+ auto fileName = dirIt.next();
+ if (!doAddFile(fileName))
+ continue;
+ deletionCandidates.removeAll(fileName);
+ }
+ QVector<AppEntry> removedApps = removeEntries(deletionCandidates);
+ endResetModel();
+
+ // Announce the apps we removed after the model has been updated
+ for (const auto& app : removedApps) {
+ qCDebug(apps) << "Going to remove entry for " << app.sourceFileName;
+ emit appRemoved(app);
+ }
+}
+
+bool AppListModel::doAddFile(const QString& fileName)
+{
+ bool ok;
+ auto newEntry = AppParser::parseFile(fileName, &ok);
+ if (!ok)
+ return false;
+
+ for (int i = 0; i < m_rows.count(); ++i) {
+ if (m_rows[i].sourceFileName == fileName) {
+ m_rows[i] = newEntry;
+ return true;
+ }
+ }
+
+ m_rows.push_back(newEntry);
+ return true;
+}
+
+QVector<QString> AppListModel::entriesWithPrefix(const QString& prefix) const
+{
+ QVector<QString> entries;
+
+ for (const AppEntry& entry : m_rows)
+ if (entry.sourceFileName.startsWith(prefix))
+ entries.push_back(entry.sourceFileName);
+ return entries;
+}
+
+QVector<AppEntry> AppListModel::removeEntries(const QVector<QString>& fileNames)
+{
+ QVector<AppEntry> removedEntries(fileNames.size());
+
+ // Rare but quadratic. The actual removal.
+ for (const auto &toRemoveFile: fileNames) {
+ for (int i = 0; i < m_rows.size(); ++i) {
+ if (m_rows[i].sourceFileName != toRemoveFile)
+ continue;
+ removedEntries.append(m_rows[i]);
+ m_rows.removeAt(i);
+ break;
+ }
+ }
+
+ return removedEntries;
+}
+
+QVariant AppListModel::findApplicationId(const QString& appId) const
+{
+ for (const auto& entry : m_rows) {
+ if (entry.appId == appId)
+ return QVariant::fromValue(entry);
+ }
+
+ return QVariant();
+}
diff --git a/wayland/democompositor/apps/applistmodel.h b/wayland/democompositor/apps/applistmodel.h
new file mode 100644
index 0000000..f2e61cc
--- /dev/null
+++ b/wayland/democompositor/apps/applistmodel.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 once
+
+#include "appentry.h"
+
+#include <QtCore/QAbstractListModel>
+#include <QtCore/QVector>
+
+/**
+ * A model that holds all available applications and
+ * exports them to a QML scene
+ */
+class AppListModel : public QAbstractListModel {
+ Q_OBJECT
+public:
+
+ enum Roles {
+ App = Qt::UserRole,
+ IconName,
+ ApplicationName,
+ ApplicationId,
+ ExeuctableName,
+ ExecutablePath,
+ SourceFileName,
+ };
+
+ int rowCount(const QModelIndex& parent) const Q_DECL_OVERRIDE;
+ QVariant data(const QModelIndex& index, int role) const Q_DECL_OVERRIDE;
+ QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
+
+ Q_INVOKABLE QVariant findApplicationId(const QString& appId) const;
+
+Q_SIGNALS:
+ void appRemoved(const AppEntry& appEntry);
+
+public Q_SLOTS:
+ bool addAndWatchDir(const QString& dirName);
+
+ void addFile(const QString& fileName);
+ void addDir(const QString& dirName);
+
+private:
+ QVector<QString> entriesWithPrefix(const QString& prefix) const;
+ QVector<AppEntry> removeEntries(const QVector<QString>& sourceFileNames);
+ bool doAddFile(const QString& fileName);
+
+ QVector<AppEntry> m_rows;
+ static QHash<int, QByteArray> m_roles;
+};
diff --git a/wayland/democompositor/apps/applog.h b/wayland/democompositor/apps/applog.h
new file mode 100644
index 0000000..b116530
--- /dev/null
+++ b/wayland/democompositor/apps/applog.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 once
+
+#include <QtCore/QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(apps)
diff --git a/wayland/democompositor/apps/appparser.cpp b/wayland/democompositor/apps/appparser.cpp
new file mode 100644
index 0000000..528cb8f
--- /dev/null
+++ b/wayland/democompositor/apps/appparser.cpp
@@ -0,0 +1,170 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 "appparser.h"
+#include "applog.h"
+
+#include <QtCore/QFile>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+
+Q_LOGGING_CATEGORY(apps, "launcher.apps")
+
+
+static QString doReadString(const QJsonValue& value, bool *ok)
+{
+ if (value.type() != QJsonValue::String) {
+ *ok = false;
+ return QString();
+ }
+ return value.toString();
+}
+
+static int doReadInt(const QJsonValue& value, bool *ok)
+{
+ if (value.type() != QJsonValue::Double) {
+ *ok = false;
+ return 0;
+ }
+ return value.toInt();
+}
+
+static QString readString(const QJsonObject& object, const QString& key, bool *ok)
+{
+ return doReadString(object.value(key), ok);
+}
+
+static QString readStringOptional(const QJsonObject& object, const QString& key, bool *ok)
+{
+ auto item = object.value(key);
+ if (item.type() == QJsonValue::Undefined)
+ return QString();
+ return doReadString(item, ok);
+}
+
+static int readInt(const QJsonObject& object, const QString& key, bool *ok)
+{
+ return doReadInt(object.value(key), ok);
+}
+
+static QVariantMap readExtensions(const QJsonObject& object, const QString& prefixKey)
+{
+ QVariantMap map;
+
+ for (auto it = object.constBegin(); it != object.constEnd(); ++it) {
+ if (!it.key().startsWith(prefixKey))
+ continue;
+ map[it.key()] = it.value().toVariant();
+ }
+ return map;
+}
+
+AppEntry AppParser::parseData(const QByteArray& content, const QString& fileName, bool *ok)
+{
+ *ok = true;
+ QJsonParseError error;
+
+ QJsonDocument doc = QJsonDocument::fromJson(content, &error);
+ if (error.error != QJsonParseError::NoError) {
+ qCWarning(apps) << "Failed to parse json: " << error.errorString();
+ *ok = false;
+ return AppEntry::empty();
+ }
+
+ if (!doc.isObject()) {
+ qCWarning(apps) << "Parsed document is not an object";
+ *ok = false;
+ return AppEntry::empty();
+ }
+
+ auto root = doc.object();
+
+ QString type = readString(root, QStringLiteral("Type"), ok);
+ if (type != QStringLiteral("Application")) {
+ qCWarning(apps) << "Unknown type" << type;
+ *ok = false;
+ }
+
+ int version = readInt(root, QStringLiteral("Version"), ok);
+ if (version != 1) {
+ qCWarning(apps) << "Version number should be 1... Consider to fix that" << version;
+ }
+
+ QString iconName = readString(root, QStringLiteral("Icon"), ok);
+ QString appName = readString(root, QStringLiteral("Name"), ok);
+ QString appId = readString(root, QStringLiteral("Id"), ok);
+ QString executableName = readString(root, QStringLiteral("Exec"), ok);
+ QString executablePath = readStringOptional(root, QStringLiteral("Path"), ok);
+ QVariantMap extensions = readExtensions(root, QStringLiteral("X-"));
+ if (!*ok)
+ return AppEntry::empty();
+
+ return AppEntry{iconName, appName, appId, executableName, executablePath, fileName, extensions};
+}
+
+AppEntry AppParser::parseFile(const QString& fileName, bool *ok)
+{
+ qCDebug(apps) << "Trying to parse" << fileName;
+
+ QFile file(fileName);
+ if (!file.open(QFile::ReadOnly)) {
+ qCWarning(apps) << "Failed to open" << fileName;
+ *ok = false;
+ return AppEntry::empty();
+ }
+
+ auto entry = parseData(file.readAll(), fileName, ok);
+ file.close();
+ if (!*ok) {
+ qCWarning(apps) << "Failed to parse" << fileName;
+ return AppEntry::empty();
+ }
+ return entry;
+}
diff --git a/wayland/democompositor/apps/appparser.h b/wayland/democompositor/apps/appparser.h
new file mode 100644
index 0000000..a6711b4
--- /dev/null
+++ b/wayland/democompositor/apps/appparser.h
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 once
+
+#include "appentry.h"
+
+/**
+ * Parse a single application entry to an AppEntry.
+ */
+class AppParser {
+public:
+ static AppEntry parseData(const QByteArray& content, const QString& fileName, bool *ok);
+ static AppEntry parseFile(const QString& fileName, bool *ok);
+};
diff --git a/wayland/democompositor/apps/apps.pri b/wayland/democompositor/apps/apps.pri
new file mode 100644
index 0000000..9b19060
--- /dev/null
+++ b/wayland/democompositor/apps/apps.pri
@@ -0,0 +1,11 @@
+
+HEADERS += \
+ apps/appentry.h \
+ apps/applog.h \
+ apps/applistmodel.h \
+ apps/appparser.h
+
+SOURCES += \
+ apps/appentry.cpp \
+ apps/applistmodel.cpp \
+ apps/appparser.cpp
diff --git a/wayland/democompositor/democompositor.pro b/wayland/democompositor/democompositor.pro
index 99b6f93..22b7d98 100644
--- a/wayland/democompositor/democompositor.pro
+++ b/wayland/democompositor/democompositor.pro
@@ -17,5 +17,12 @@ RESOURCES += democompositor.qrc
HEADERS += \
processlauncher.h
-INSTALLS += target
+# include modules
+include(apps/apps.pri)
+
+INSTALLS += target apps
target.path = /data/user/$$TARGET
+
+# TODO. How does /data/user/$$TARGET map to QStandardPaths
+apps.path = /data/user/$$TARGET/apps/
+apps.files = resources/apps/*.app
diff --git a/wayland/democompositor/democompositor.qrc b/wayland/democompositor/democompositor.qrc
index 940f04e..6e8bd54 100644
--- a/wayland/democompositor/democompositor.qrc
+++ b/wayland/democompositor/democompositor.qrc
@@ -6,15 +6,15 @@
<file>qml/MyButton.qml</file>
<file>qml/LaunchButton.qml</file>
<file>qml/TimedButton.qml</file>
- <file>images/quit.png</file>
- <file>images/greendot.png</file>
- <file>images/reddot.png</file>
- <file>images/graydot.png</file>
- <file>images/graydots.png</file>
- <file>images/greendots.png</file>
- <file>images/Icon_Clocks.png</file>
- <file>images/Icon_Maps.png</file>
- <file>images/Icon_RSS.png</file>
- <file>images/Icon_StocQt.png</file>
+ <file alias="images/quit.png">resources/images/quit.png</file>
+ <file alias="images/greendot.png">resources/images/greendot.png</file>
+ <file alias="images/reddot.png">resources/images/reddot.png</file>
+ <file alias="images/graydot.png">resources/images/graydot.png</file>
+ <file alias="images/graydots.png">resources/images/graydots.png</file>
+ <file alias="images/greendots.png">resources/images/greendots.png</file>
+ <file alias="images/Icon_Clocks.png">resources/images/Icon_Clocks.png</file>
+ <file alias="images/Icon_Maps.png">resources/images/Icon_Maps.png</file>
+ <file alias="images/Icon_RSS.png">resources/images/Icon_RSS.png</file>
+ <file alias="images/Icon_StocQt.png">resources/images/Icon_StocQt.png</file>
</qresource>
</RCC>
diff --git a/wayland/democompositor/democompositor_all.pro b/wayland/democompositor/democompositor_all.pro
new file mode 100644
index 0000000..52ae399
--- /dev/null
+++ b/wayland/democompositor/democompositor_all.pro
@@ -0,0 +1,5 @@
+TEMPLATE = subdirs
+SUBDIRS = \
+ democompositor.pro \
+ tests/apps \
+ tests/applist
diff --git a/wayland/democompositor/main.cpp b/wayland/democompositor/main.cpp
index a094a20..2b1279d 100644
--- a/wayland/democompositor/main.cpp
+++ b/wayland/democompositor/main.cpp
@@ -48,6 +48,8 @@
**
****************************************************************************/
+#include <QtCore/QCommandLineOption>
+#include <QtCore/QCommandLineParser>
#include <QtCore/QUrl>
#include <QtCore/QDebug>
@@ -55,11 +57,14 @@
#include <QtGui/QFont>
#include <QtQml/qqml.h>
#include <QtQml/QQmlApplicationEngine>
+
+#include "apps/applistmodel.h"
#include "processlauncher.h"
static void registerTypes()
{
qmlRegisterType<WaylandProcessLauncher>("com.theqtcompany.wlprocesslauncher", 1, 0, "ProcessLauncher");
+ qmlRegisterType<AppListModel>("com.theqtcompany.wlapplistmodel", 1, 0, "AppListModel");
}
@@ -68,13 +73,38 @@ int main(int argc, char *argv[])
{
qputenv("QT_QPA_EGLFS_HIDECURSOR", "1");
QGuiApplication app(argc, argv);
+ QGuiApplication::setApplicationName("democompositor");
+ QGuiApplication::setApplicationVersion("1.0");
+
+ /* Parse options */
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Demo Compositor");
+ parser.addHelpOption();
+ parser.addVersionOption();
+
+ QCommandLineOption fontNameOpt(QStringList() << "f" << "font",
+ QCoreApplication::translate("main", "Default font to use"),
+ QCoreApplication::translate("main", "Name of the font"),
+ "Open Sans");
+ parser.addOption(fontNameOpt);
+ QCommandLineOption fontNameSzeOpt(QStringList() << "s" << "size",
+ QCoreApplication::translate("main", "Default font size to use"),
+ QCoreApplication::translate("main", "Point size of the font"),
+ "12");
+ parser.addOption(fontNameSzeOpt);
+ QCommandLineOption qmlUrlOpt(QStringList() << "o" << "open",
+ QCoreApplication::translate("main", "QML scene to open"),
+ QCoreApplication::translate("main", "URL"),
+ "qrc:///qml/main.qml");
+ parser.addOption(qmlUrlOpt);
+ parser.process(app);
- QFont f("Open Sans", 12.5);
+ QFont f(parser.value(fontNameOpt), parser.value(fontNameSzeOpt).toInt());
app.setFont(f);
qputenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1");
registerTypes();
- QQmlApplicationEngine appEngine(QUrl("qrc:///qml/main.qml"));
+ QQmlApplicationEngine appEngine(QUrl(parser.value(qmlUrlOpt)));
return app.exec();
}
diff --git a/wayland/democompositor/processlauncher.cpp b/wayland/democompositor/processlauncher.cpp
index a25fbd5..29e4d64 100644
--- a/wayland/democompositor/processlauncher.cpp
+++ b/wayland/democompositor/processlauncher.cpp
@@ -49,8 +49,22 @@
****************************************************************************/
#include "processlauncher.h"
+#include "apps/appentry.h"
#include <QProcess>
+#include <QTimer>
+
+Q_LOGGING_CATEGORY(procs, "launcher.procs")
+
+/*
+ * Two AppState's are equal if they are managing the same
+ * QProcess. It is assumed that no AppState survives beyond
+ * the QProcess.
+ */
+bool operator==(const AppState& lhs, const AppState& rhs)
+{
+ return lhs.process == rhs.process;
+}
WaylandProcessLauncher::WaylandProcessLauncher(QObject *parent)
: QObject(parent)
@@ -59,18 +73,114 @@ WaylandProcessLauncher::WaylandProcessLauncher(QObject *parent)
WaylandProcessLauncher::~WaylandProcessLauncher()
{
+ for (auto state : m_appStates) {
+ state.process->disconnect(state.finishedConn);
+ state.process->disconnect(state.errorOccurredConn);
+ state.process->disconnect(state.startedConn);
+ delete state.process;
+ }
+ m_appStates.clear();
+}
+
+QVariant WaylandProcessLauncher::appStateForPid(int pid) const
+{
+ for (auto state : m_appStates) {
+ if (state.process->pid() == pid) {
+ qCDebug(procs) << "Found state for" << pid << state.appEntry.executableName;
+ return QVariant::fromValue(state);
+ }
+ }
+
+ qCDebug(procs) << "Couldn't find entry for" << pid;
+ return QVariant();
+}
+
+bool WaylandProcessLauncher::isRunning(const AppEntry& entry) const
+{
+ for (auto state : m_appStates) {
+ if (state.appEntry.sourceFileName == entry.sourceFileName) {
+ qCDebug(procs) << "AppEntry associated to a state" << entry.executableName;
+ return true;
+ }
+ }
+
+ qCDebug(procs) << "AppEntry not associated to a state " << entry.executableName;
+ return false;
+}
+
+void WaylandProcessLauncher::kill(const AppEntry& entry)
+{
+ for (auto state : m_appStates) {
+ if (state.appEntry.sourceFileName != entry.sourceFileName)
+ continue;
+
+ qCDebug(procs) << "Killing process " << state.process->pid() << " for " << entry.sourceFileName;
+ state.process->kill();
+ }
}
-void WaylandProcessLauncher::launch(const QString &program)
+void WaylandProcessLauncher::stop(const AppEntry& entry, int ms)
{
+ for (auto state : m_appStates) {
+ if (state.appEntry.sourceFileName != entry.sourceFileName)
+ continue;
+
+ auto timer = new QTimer(state.process);
+ connect(timer, &QTimer::timeout, [entry, state, timer] {
+ qCDebug(procs) << "Sending SIGKILL " << state.process->pid() << " for " << entry.sourceFileName;
+ timer->deleteLater();
+ state.process->kill();
+ });
+ timer->start(ms);
+ qCDebug(procs) << "Sending SIGTERM " << state.process->pid() << " for " << entry.sourceFileName;
+ state.process->terminate();
+ }
+}
+
+void WaylandProcessLauncher::launch(const AppEntry &entry)
+{
+ qCDebug(procs) << "Launching" << entry.executableName;
+
QProcess *process = new QProcess(this);
- connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
- process, &QProcess::deleteLater);
- connect(process, &QProcess::errorOccurred, &QProcess::deleteLater);
+ process->setProcessChannelMode(QProcess::ForwardedChannels);
+
+ QMetaObject::Connection conn;
+ AppState state{process, entry, conn, conn, conn};
+ m_appStates.push_back(state);
+
+ /* handle potential errors and life cycle */
+ state.finishedConn = connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
+ [state, this](int exitCode, QProcess::ExitStatus status) {
+ qCDebug(procs) << "AppEntry finished" << state.appEntry.executableName << exitCode << status;
+ emit appFinished(state, exitCode, status);
+ m_appStates.removeOne(state);
+ state.process->deleteLater();
+ });
+ state.errorOccurredConn = connect(process, &QProcess::errorOccurred,
+ [state, this](QProcess::ProcessError err) {
+ qCDebug(procs) << "AppEntry error occurred" << state.appEntry.executableName << err;
+
+ /* Maybe finished was already emitted. Let's not emit an error after that */
+ if (!m_appStates.removeOne(state))
+ return;
+
+ if (err == QProcess::FailedToStart || err == QProcess::UnknownError)
+ emit appNotStarted(state);
+ state.process->deleteLater();
+ });
+ state.startedConn = connect(process, &QProcess::started,
+ [state, this]() {
+ qCDebug(procs) << "AppEntry started" << state.appEntry.executableName;
+ emit appStarted(state);
+ });
+
+ if (!entry.executablePath.isNull()) {
+ auto env = QProcessEnvironment::systemEnvironment();
+ env.insert(QStringLiteral("PATH"), entry.executablePath);
+ process->setProcessEnvironment(env);
+ }
QStringList arguments;
arguments << "-platform" << "wayland";
- process->start(program, arguments);
-
+ process->start(entry.executableName, arguments);
}
-
diff --git a/wayland/democompositor/processlauncher.h b/wayland/democompositor/processlauncher.h
index 261da6d..ae847e2 100644
--- a/wayland/democompositor/processlauncher.h
+++ b/wayland/democompositor/processlauncher.h
@@ -51,7 +51,29 @@
#ifndef PROCESSLAUNCHER_H
#define PROCESSLAUNCHER_H
+#include "apps/appentry.h"
+
#include <QObject>
+#include <QProcess>
+#include <QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(procs)
+
+/**
+ * Transient class. Do not keep AppState beyond the
+ * finished signal.
+ */
+class AppState {
+ Q_GADGET
+ Q_PROPERTY(QProcess *process MEMBER process CONSTANT)
+ Q_PROPERTY(AppEntry appEntry MEMBER appEntry CONSTANT)
+public:
+ QProcess *process;
+ AppEntry appEntry;
+ QMetaObject::Connection finishedConn;
+ QMetaObject::Connection errorOccurredConn;
+ QMetaObject::Connection startedConn;
+};
class WaylandProcessLauncher : public QObject
{
@@ -60,7 +82,22 @@ class WaylandProcessLauncher : public QObject
public:
explicit WaylandProcessLauncher(QObject *parent = 0);
~WaylandProcessLauncher();
- Q_INVOKABLE void launch(const QString &program);
+ Q_INVOKABLE void launch(const AppEntry &entry);
+
+ Q_INVOKABLE QVariant appStateForPid(int pid) const;
+ Q_INVOKABLE bool isRunning(const AppEntry& entry) const;
+ Q_INVOKABLE void kill(const AppEntry& entry);
+ Q_INVOKABLE void stop(const AppEntry& entry, int timeout_ms);
+
+Q_SIGNALS:
+ void appStarted(const AppState &appState);
+ void appFinished(const AppState &appState, int exitCode, QProcess::ExitStatus exitStatus);
+ void appNotStarted(const AppState& appState);
+
+private:
+ QVector<AppState> m_appStates;
};
+Q_DECLARE_METATYPE(AppState)
+
#endif // PROCESSLAUNCHER_H
diff --git a/wayland/democompositor/qml/Chrome.qml b/wayland/democompositor/qml/Chrome.qml
index 33a5b99..9c21cd1 100644
--- a/wayland/democompositor/qml/Chrome.qml
+++ b/wayland/democompositor/qml/Chrome.qml
@@ -65,6 +65,8 @@ Rectangle {
property int marginWidth : 5
property int titlebarHeight : 5
+ property var appEntry
+
function requestSize(w, h) {
surfaceItem.requestSize(Qt.size(w - 2 * marginWidth, h - titlebarHeight - marginWidth))
}
diff --git a/wayland/democompositor/qml/LaunchButton.qml b/wayland/democompositor/qml/LaunchButton.qml
index deb462c..80a057a 100644
--- a/wayland/democompositor/qml/LaunchButton.qml
+++ b/wayland/democompositor/qml/LaunchButton.qml
@@ -51,11 +51,14 @@
import QtQuick 2.6
MyButton {
- property string executable
+ property var appEntry
text.text: "Uninitialized"
text.elide: Text.ElideRight
text.maximumLineCount: 1
iconSize: 32
- onTriggered: launcher.launch(executable)
+ onTriggered: {
+ if (!launcher.isRunning(appEntry))
+ launcher.launch(appEntry)
+ }
}
diff --git a/wayland/democompositor/qml/Screen.qml b/wayland/democompositor/qml/Screen.qml
index 34fd090..65df18f 100644
--- a/wayland/democompositor/qml/Screen.qml
+++ b/wayland/democompositor/qml/Screen.qml
@@ -53,11 +53,13 @@ import QtQuick.Window 2.2
import QtWayland.Compositor 1.0
import com.theqtcompany.wlprocesslauncher 1.0
+import com.theqtcompany.wlapplistmodel 1.0
WaylandOutput {
id: output
property alias surfaceArea: background
+ property alias appLauncher: launcher
property var windowList: [ ]
property int hiddenWindowCount
@@ -85,6 +87,17 @@ WaylandOutput {
id: launcher
}
+ AppListModel {
+ id: apps
+ onAppRemoved: {
+ console.log("Application was removed: " + appEntry.appName);
+ launcher.kill(appEntry);
+ }
+ }
+
+ Component.onCompleted: {
+ apps.addAndWatchDir("/data/user/democompositor/apps/")
+ }
Rectangle {
id: curtain
@@ -176,7 +189,7 @@ WaylandOutput {
pressedColor: pressedCol
text.maximumLineCount: 1
- text.text: modelData.shellSurface.title.length > 0 ? modelData.shellSurface.title : "Untitled"
+ text.text: winItem.appEntry == null ? "Untitled" : winItem.appEntry.appName
text.elide: Text.ElideRight
text.color: textCol
onTriggered: {
@@ -189,7 +202,9 @@ WaylandOutput {
}
onSlideTrigger: {
//console.log("slide " + winItem + " : " + winItem.shellSurface.surface)
- winItem.shellSurface.surface.client.close()
+ winItem.appEntry == null ?
+ winItem.shellSurface.surface.client.close() :
+ launcher.stop(winItem.appEntry, 5000);
}
}
Rectangle {
@@ -252,46 +267,20 @@ WaylandOutput {
height: 5
width: 1
}
- LaunchButton {
- height: 60
- width: sidebar.width - 10
- buttonColor: backgroundCol
- pressedColor: pressedCol
- textColor: textCol
- text.text: "Clocks"
- executable: "./clocks"
- icon.source: "qrc:/images/Icon_Clocks.png"
- }
- LaunchButton {
- height: 60
- width: sidebar.width - 10
- buttonColor: backgroundCol
- pressedColor: pressedCol
- textColor: textCol
- text.text: "RSS News"
- executable: "./rssnews"
- icon.source: "qrc:/images/Icon_RSS.png"
- }
- LaunchButton {
- height: 60
- width: sidebar.width - 10
- buttonColor: backgroundCol
- pressedColor: pressedCol
- textColor: textCol
- text.text: "StoQt"
- executable: "./stocqt"
- icon.source: "qrc:/images/Icon_StocQt.png"
- }
- LaunchButton {
- height: 60
- width: sidebar.width - 10
- buttonColor: backgroundCol
- pressedColor: pressedCol
- textColor: textCol
- text.text: "Maps"
- executable: "./qml_location_mapviewer"
- icon.source: "qrc:/images/Icon_Maps.png"
+ Repeater {
+ model: apps
+
+ LaunchButton {
+ height: 60
+ width: sidebar.width - 10
+ buttonColor: backgroundCol
+ pressedColor: pressedCol
+ textColor: textCol
+ text.text: model.applicationName
+ appEntry: model.appEntry
+ icon.source: model.iconName
+ }
}
TimedButton {
diff --git a/wayland/democompositor/qml/main.qml b/wayland/democompositor/qml/main.qml
index 8c0c621..3947b52 100644
--- a/wayland/democompositor/qml/main.qml
+++ b/wayland/democompositor/qml/main.qml
@@ -57,6 +57,7 @@ WaylandCompositor {
property var primarySurfacesArea: null
Screen {
+ id: mainScreen
compositor: comp
}
@@ -69,7 +70,18 @@ WaylandCompositor {
WlShell {
id: defaultShell
onWlShellSurfaceCreated: {
- chromeComponent.createObject(defaultOutput.surfaceArea, { "shellSurface": shellSurface } );
+ const pid = shellSurface.surface.client.processId;
+ const appState = mainScreen.appLauncher.appStateForPid(pid);
+ var appEntry;
+ if (!appState) {
+ console.log("shellSurface of unknown application. Continuing. PID=" + pid);
+ } else {
+ console.log("shellSurface belonging to " + appState.appEntry.executableName);
+ appEntry = appState.appEntry;
+ }
+ chromeComponent.createObject(defaultOutput.surfaceArea, {
+ "shellSurface": shellSurface,
+ "appEntry": appEntry } );
defaultOutput.relayout();
}
}
diff --git a/wayland/democompositor/resources/apps/clock.app b/wayland/democompositor/resources/apps/clock.app
new file mode 100644
index 0000000..6ddf0d2
--- /dev/null
+++ b/wayland/democompositor/resources/apps/clock.app
@@ -0,0 +1,12 @@
+{
+ "Type": "Application",
+ "Version": 1,
+ "Icon": "qrc:/images/Icon_Clocks.png",
+ "Name": "Clocks",
+ "Id": "io.qt.clocks",
+ "Exec": "clocks",
+ "Path": "./",
+ "X-Fullscreen": true,
+ "X-Priority": 100,
+ "X-Screen": "left"
+}
diff --git a/wayland/democompositor/resources/apps/maps.app b/wayland/democompositor/resources/apps/maps.app
new file mode 100644
index 0000000..0192e4b
--- /dev/null
+++ b/wayland/democompositor/resources/apps/maps.app
@@ -0,0 +1,9 @@
+{
+ "Type": "Application",
+ "Version": 1,
+ "Icon": "qrc:/images/Icon_Maps.png",
+ "Name": "Maps",
+ "Id": "io.qt.maps",
+ "Exec": "qml_location_mapviewer",
+ "Path": "./"
+}
diff --git a/wayland/democompositor/resources/apps/rss.app b/wayland/democompositor/resources/apps/rss.app
new file mode 100644
index 0000000..d631cda
--- /dev/null
+++ b/wayland/democompositor/resources/apps/rss.app
@@ -0,0 +1,9 @@
+{
+ "Type": "Application",
+ "Version": 1,
+ "Icon": "qrc:/images/Icon_RSS.png",
+ "Name": "RSS News",
+ "Id": "io.qt.rssnews",
+ "Exec": "rssnews",
+ "Path": "./"
+}
diff --git a/wayland/democompositor/resources/apps/stocqt.app b/wayland/democompositor/resources/apps/stocqt.app
new file mode 100644
index 0000000..692f9ce
--- /dev/null
+++ b/wayland/democompositor/resources/apps/stocqt.app
@@ -0,0 +1,9 @@
+{
+ "Type": "Application",
+ "Version": 1,
+ "Icon": "qrc:/images/Icon_StocQt.png",
+ "Name": "StoQt",
+ "Id": "io.qt.stoqt",
+ "Exec": "stocqt",
+ "Path": "./"
+}
diff --git a/wayland/democompositor/images/Icon_Clocks.png b/wayland/democompositor/resources/images/Icon_Clocks.png
index 0ca1955..0ca1955 100644
--- a/wayland/democompositor/images/Icon_Clocks.png
+++ b/wayland/democompositor/resources/images/Icon_Clocks.png
Binary files differ
diff --git a/wayland/democompositor/images/Icon_Maps.png b/wayland/democompositor/resources/images/Icon_Maps.png
index fac0dc9..fac0dc9 100644
--- a/wayland/democompositor/images/Icon_Maps.png
+++ b/wayland/democompositor/resources/images/Icon_Maps.png
Binary files differ
diff --git a/wayland/democompositor/images/Icon_RSS.png b/wayland/democompositor/resources/images/Icon_RSS.png
index 54ada4f..54ada4f 100644
--- a/wayland/democompositor/images/Icon_RSS.png
+++ b/wayland/democompositor/resources/images/Icon_RSS.png
Binary files differ
diff --git a/wayland/democompositor/images/Icon_StocQt.png b/wayland/democompositor/resources/images/Icon_StocQt.png
index d12aec1..d12aec1 100644
--- a/wayland/democompositor/images/Icon_StocQt.png
+++ b/wayland/democompositor/resources/images/Icon_StocQt.png
Binary files differ
diff --git a/wayland/democompositor/images/graydot.png b/wayland/democompositor/resources/images/graydot.png
index 99606e3..99606e3 100644
--- a/wayland/democompositor/images/graydot.png
+++ b/wayland/democompositor/resources/images/graydot.png
Binary files differ
diff --git a/wayland/democompositor/images/graydots.png b/wayland/democompositor/resources/images/graydots.png
index 0f90f12..0f90f12 100644
--- a/wayland/democompositor/images/graydots.png
+++ b/wayland/democompositor/resources/images/graydots.png
Binary files differ
diff --git a/wayland/democompositor/images/greendot.png b/wayland/democompositor/resources/images/greendot.png
index 0afb08f..0afb08f 100644
--- a/wayland/democompositor/images/greendot.png
+++ b/wayland/democompositor/resources/images/greendot.png
Binary files differ
diff --git a/wayland/democompositor/images/greendots.png b/wayland/democompositor/resources/images/greendots.png
index ab1e220..ab1e220 100644
--- a/wayland/democompositor/images/greendots.png
+++ b/wayland/democompositor/resources/images/greendots.png
Binary files differ
diff --git a/wayland/democompositor/images/icon1.png b/wayland/democompositor/resources/images/icon1.png
index 08f1c0c..08f1c0c 100644
--- a/wayland/democompositor/images/icon1.png
+++ b/wayland/democompositor/resources/images/icon1.png
Binary files differ
diff --git a/wayland/democompositor/images/icon2.png b/wayland/democompositor/resources/images/icon2.png
index e02dda8..e02dda8 100644
--- a/wayland/democompositor/images/icon2.png
+++ b/wayland/democompositor/resources/images/icon2.png
Binary files differ
diff --git a/wayland/democompositor/images/icon3.png b/wayland/democompositor/resources/images/icon3.png
index 9872b89..9872b89 100644
--- a/wayland/democompositor/images/icon3.png
+++ b/wayland/democompositor/resources/images/icon3.png
Binary files differ
diff --git a/wayland/democompositor/images/icon4.png b/wayland/democompositor/resources/images/icon4.png
index 3f64825..3f64825 100644
--- a/wayland/democompositor/images/icon4.png
+++ b/wayland/democompositor/resources/images/icon4.png
Binary files differ
diff --git a/wayland/democompositor/images/quit.png b/wayland/democompositor/resources/images/quit.png
index 7366d77..7366d77 100644
--- a/wayland/democompositor/images/quit.png
+++ b/wayland/democompositor/resources/images/quit.png
Binary files differ
diff --git a/wayland/democompositor/images/reddot.png b/wayland/democompositor/resources/images/reddot.png
index 06451be..06451be 100644
--- a/wayland/democompositor/images/reddot.png
+++ b/wayland/democompositor/resources/images/reddot.png
Binary files differ
diff --git a/wayland/democompositor/tests/applist/applist.pro b/wayland/democompositor/tests/applist/applist.pro
new file mode 100644
index 0000000..e253b66
--- /dev/null
+++ b/wayland/democompositor/tests/applist/applist.pro
@@ -0,0 +1,17 @@
+TARGET = tst_applistmodel
+QT = testlib
+CONFIG += testcase
+
+INCLUDEPATH += ../../apps
+
+HEADERS += \
+ ../../apps/appentry.h \
+ ../../apps/applistmodel.h
+
+SOURCES += \
+ ../../apps/appentry.cpp \
+ ../../apps/appparser.cpp \
+ ../../apps/applistmodel.cpp
+
+SOURCES += tst_applistmodel.cpp
+RESOURCES += applist.qrc
diff --git a/wayland/democompositor/tests/applist/applist.qrc b/wayland/democompositor/tests/applist/applist.qrc
new file mode 100644
index 0000000..eb4d8da
--- /dev/null
+++ b/wayland/democompositor/tests/applist/applist.qrc
@@ -0,0 +1,6 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/">
+ <file alias="app1.json">../../resources/apps/clock.app</file>
+ <file alias="app2.json">../../resources/apps/maps.app</file>
+</qresource>
+</RCC>
diff --git a/wayland/democompositor/tests/applist/tst_applistmodel.cpp b/wayland/democompositor/tests/applist/tst_applistmodel.cpp
new file mode 100644
index 0000000..921defc
--- /dev/null
+++ b/wayland/democompositor/tests/applist/tst_applistmodel.cpp
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 "applistmodel.h"
+#include "appentry.h"
+
+#include <QtCore/QVariant>
+
+#include <QtTest>
+
+class tst_AppListModel : public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testNoDuplicates();
+ void testRoles();
+};
+
+void tst_AppListModel::testNoDuplicates()
+{
+ AppListModel model;
+ QCOMPARE(model.rowCount(QModelIndex()), 0);
+ model.addFile(":/app1.json");
+ QCOMPARE(model.rowCount(QModelIndex()), 1);
+ model.addFile(":/app2.json");
+ QCOMPARE(model.rowCount(QModelIndex()), 2);
+ model.addFile(":/app1.json");
+ QCOMPARE(model.rowCount(QModelIndex()), 2);
+ model.addFile(":/app2.json");
+}
+
+void tst_AppListModel::testRoles()
+{
+ AppListModel model;
+ model.addFile(":/app1.json");
+
+ auto idx = model.index(1, 0);
+ QVERIFY(!idx.isValid());
+ idx = model.index(0, 0);
+ QVERIFY(idx.isValid());
+
+ /* Check we get a full AppEntry back */
+ auto var = idx.data(AppListModel::App);
+ QVERIFY(var.canConvert<AppEntry>());
+ auto app = var.value<AppEntry>();
+ QCOMPARE(app.sourceFileName, QStringLiteral(":/app1.json"));
+
+ var = idx.data(AppListModel::IconName);
+ QCOMPARE(var.toString(), QStringLiteral("qrc:/images/Icon_Clocks.png"));
+ var = idx.data(AppListModel::ApplicationName);
+ QCOMPARE(var.toString(), QStringLiteral("Clocks"));
+ var = idx.data(AppListModel::ExeuctableName);
+ QCOMPARE(var.toString(), QStringLiteral("clocks"));
+ var = idx.data(AppListModel::ExecutablePath);
+ QCOMPARE(var.toString(), QStringLiteral("./"));
+ var = idx.data(AppListModel::SourceFileName);
+ QCOMPARE(var.toString(), QStringLiteral(":/app1.json"));
+}
+
+QTEST_MAIN(tst_AppListModel)
+#include "tst_applistmodel.moc"
diff --git a/wayland/democompositor/tests/apps/apps.pro b/wayland/democompositor/tests/apps/apps.pro
new file mode 100644
index 0000000..21a6cfe
--- /dev/null
+++ b/wayland/democompositor/tests/apps/apps.pro
@@ -0,0 +1,15 @@
+TARGET = tst_apps
+QT = testlib
+CONFIG += testcase
+
+INCLUDEPATH += ../../apps
+
+HEADERS += \
+ ../../apps/appentry.h
+
+SOURCES += \
+ ../../apps/appentry.cpp \
+ ../../apps/appparser.cpp
+
+SOURCES += tst_appparser.cpp
+RESOURCES += apps.qrc
diff --git a/wayland/democompositor/tests/apps/apps.qrc b/wayland/democompositor/tests/apps/apps.qrc
new file mode 100644
index 0000000..a8a834b
--- /dev/null
+++ b/wayland/democompositor/tests/apps/apps.qrc
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/">
+ <file alias="app.json">../../resources/apps/clock.app</file>
+</qresource>
+</RCC>
diff --git a/wayland/democompositor/tests/apps/tst_appparser.cpp b/wayland/democompositor/tests/apps/tst_appparser.cpp
new file mode 100644
index 0000000..38a459c
--- /dev/null
+++ b/wayland/democompositor/tests/apps/tst_appparser.cpp
@@ -0,0 +1,142 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qt for Device Creation.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 <QtTest>
+
+#include "appparser.h"
+
+class tst_AppParser : public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testValidVersion1_data();
+ void testValidVersion1();
+
+ void testInvalid_data();
+ void testInvalid();
+
+ void testFileOpen();
+};
+
+
+void tst_AppParser::testValidVersion1_data()
+{
+ QTest::addColumn<QByteArray>("input");
+ QTest::addColumn<QString>("icon");
+ QTest::addColumn<QString>("name");
+ QTest::addColumn<QString>("id");
+ QTest::addColumn<QString>("exec");
+ QTest::addColumn<QString>("path");
+
+ QTest::addRow("clock")
+ << QByteArray("{\"Type\":\"Application\", \"Version\":1, \"Icon\":\"icon\", \"Name\":\"Clocks\",\"Id\":\"io.qt.clocks\",\"Exec\":\"clocks\"}")
+ << QStringLiteral("icon") << QStringLiteral("Clocks") << QStringLiteral("io.qt.clocks") << QStringLiteral("clocks") << QString();
+ QTest::addRow("path")
+ << QByteArray("{\"Type\":\"Application\", \"Version\":1, \"Icon\":\"icon\", \"Name\":\"Clocks\",\"Id\":\"io.qt.clocks\",\"Exec\":\"clocks\",\"Path\":\"P\"}")
+ << QStringLiteral("icon") << QStringLiteral("Clocks") << QStringLiteral("io.qt.clocks") << QStringLiteral("clocks") << QStringLiteral("P");
+}
+
+void tst_AppParser::testValidVersion1()
+{
+ QFETCH(QByteArray, input);
+ QFETCH(QString, icon);
+ QFETCH(QString, name);
+ QFETCH(QString, exec);
+ QFETCH(QString, path);
+
+ bool ok = false;
+ auto entry = AppParser::parseData(input, QStringLiteral("dummy"), &ok);
+ QVERIFY(ok);
+}
+
+void tst_AppParser::testInvalid_data()
+{
+ QTest::addColumn<QByteArray>("input");
+ QTest::addRow("no json") << QByteArray("12345");
+ QTest::addRow("array") << QByteArray("[]");
+ QTest::addRow("no content") << QByteArray("{}");
+ QTest::addRow("empty") << QByteArray("");
+}
+
+void tst_AppParser::testInvalid()
+{
+ QFETCH(QByteArray, input);
+
+ bool ok = true;
+ AppParser::parseData(input, QStringLiteral("dummy"), &ok);
+ QVERIFY(!ok);
+}
+
+void tst_AppParser::testFileOpen()
+{
+ bool ok = true;
+
+ AppParser::parseFile(":/can_not_exist_here.json", &ok);
+ QVERIFY(!ok);
+
+ ok = false;
+ auto entry = AppParser::parseFile(":/app.json", &ok);
+ QVERIFY(ok);
+ QCOMPARE(entry.iconName, QStringLiteral("qrc:/images/Icon_Clocks.png"));
+ QCOMPARE(entry.appName, QStringLiteral("Clocks"));
+ QCOMPARE(entry.executableName, QStringLiteral("clocks"));
+ QCOMPARE(entry.executablePath, QStringLiteral("./"));
+
+ // Look at extensions
+ QVERIFY(entry.extensions["X-Fullscreen"].canConvert(QMetaType::Bool));
+ QCOMPARE(entry.extensions["X-Fullscreen"].toBool(), true);
+ QVERIFY(entry.extensions["X-Priority"].canConvert(QMetaType::Double));
+ QCOMPARE(entry.extensions["X-Priority"].toInt(), 100);
+ QVERIFY(entry.extensions["X-Screen"].canConvert(QMetaType::QString));
+ QCOMPARE(entry.extensions["X-Screen"].toString(), QStringLiteral("left"));
+}
+
+QTEST_MAIN(tst_AppParser)
+#include "tst_appparser.moc"