diff options
author | Lubomir I. Ivanov <neolit123@gmail.com> | 2017-11-04 21:23:37 +0200 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2017-11-05 14:48:56 -0800 |
commit | 956b45ddfda060fcd818659ee05618ed2e4bfcab (patch) | |
tree | 1acb6ff91cba57e059e7eac8868ecd066539b230 /map-widget/qml | |
parent | 6ce4239884780fafdf641fa52a2a8c7a0a02450d (diff) | |
download | subsurface-956b45ddfda060fcd818659ee05618ed2e4bfcab.tar.gz |
map-widget: move the widget and its resources to 'map-widget'
Move all the map widget platform agnostic files to the
<subsurface-root>/map-widget folder.
This avoids the confusion about the desktop version of subsurface
using mobile components. The map widget is planned as a shared
component between the mobile and desktop versions.
desktop-widgets/mapwidget[.h/.cpp] still remain as those are specific
to the desktop version.
Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com>
Diffstat (limited to 'map-widget/qml')
-rw-r--r-- | map-widget/qml/MapWidget.qml | 355 | ||||
-rw-r--r-- | map-widget/qml/MapWidgetContextMenu.qml | 124 | ||||
-rw-r--r-- | map-widget/qml/MapWidgetError.qml | 13 | ||||
-rw-r--r-- | map-widget/qml/icons/mapwidget-context-menu.png | bin | 0 -> 242 bytes | |||
-rw-r--r-- | map-widget/qml/icons/mapwidget-marker-gray.png | bin | 0 -> 2033 bytes | |||
-rw-r--r-- | map-widget/qml/icons/mapwidget-marker-selected.png | bin | 0 -> 1995 bytes | |||
-rw-r--r-- | map-widget/qml/icons/mapwidget-marker.png | bin | 0 -> 1801 bytes | |||
-rw-r--r-- | map-widget/qml/icons/mapwidget-toggle-satellite.png | bin | 0 -> 6288 bytes | |||
-rw-r--r-- | map-widget/qml/icons/mapwidget-toggle-street.png | bin | 0 -> 5916 bytes | |||
-rw-r--r-- | map-widget/qml/icons/mapwidget-zoom-in.png | bin | 0 -> 256 bytes | |||
-rw-r--r-- | map-widget/qml/icons/mapwidget-zoom-out.png | bin | 0 -> 242 bytes |
11 files changed, 492 insertions, 0 deletions
diff --git a/map-widget/qml/MapWidget.qml b/map-widget/qml/MapWidget.qml new file mode 100644 index 000000000..2a3283482 --- /dev/null +++ b/map-widget/qml/MapWidget.qml @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +import QtQuick 2.0 +import QtLocation 5.3 +import QtPositioning 5.3 +import org.subsurfacedivelog.mobile 1.0 + +Item { + id: rootItem + property int nSelectedDives: 0 + + MapWidgetHelper { + id: mapHelper + map: map + editMode: false + onSelectedDivesChanged: nSelectedDives = list.length + onEditModeChanged: editMessage.isVisible = editMode === true ? 1 : 0 + onCoordinatesChanged: {} + Component.onCompleted: { + map.plugin = Qt.createQmlObject(pluginObject, rootItem) + map.mapType = { "STREET": map.supportedMapTypes[0], "SATELLITE": map.supportedMapTypes[1] } + map.activeMapType = map.mapType.SATELLITE + } + } + + Map { + id: map + anchors.fill: parent + zoomLevel: defaultZoomIn + + property var mapType + readonly property var defaultCenter: QtPositioning.coordinate(0, 0) + readonly property real defaultZoomIn: 12.0 + readonly property real defaultZoomOut: 1.0 + readonly property real textVisibleZoom: 11.0 + readonly property real zoomStep: 2.0 + property var newCenter: defaultCenter + property real newZoom: 1.0 + property real newZoomOut: 1.0 + property var clickCoord: QtPositioning.coordinate(0, 0) + property bool isReady: false + + Component.onCompleted: isReady = true + onZoomLevelChanged: { + if (isReady) + mapHelper.calculateSmallCircleRadius(map.center) + } + + MapItemView { + id: mapItemView + model: mapHelper.model + delegate: MapQuickItem { + id: mapItem + anchorPoint.x: 0 + anchorPoint.y: mapItemImage.height + coordinate: model.coordinate + z: mapHelper.model.selectedUuid === model.uuid ? mapHelper.model.count - 1 : 0 + sourceItem: Image { + id: mapItemImage + source: "qrc:///mapwidget-marker" + (mapHelper.model.selectedUuid === model.uuid ? "-selected" : (mapHelper.editMode ? "-gray" : "")) + SequentialAnimation { + id: mapItemImageAnimation + PropertyAnimation { target: mapItemImage; property: "scale"; from: 1.0; to: 0.7; duration: 120 } + PropertyAnimation { target: mapItemImage; property: "scale"; from: 0.7; to: 1.0; duration: 80 } + } + MouseArea { + drag.target: (mapHelper.editMode && mapHelper.model.selectedUuid === model.uuid) ? mapItem : undefined + anchors.fill: parent + onClicked: { + if (!mapHelper.editMode) + mapHelper.model.setSelectedUuid(model.uuid, true) + } + onDoubleClicked: map.doubleClickHandler(mapItem.coordinate) + onReleased: { + if (mapHelper.editMode && mapHelper.model.selectedUuid === model.uuid) { + mapHelper.updateCurrentDiveSiteCoordinates(mapHelper.model.selectedUuid, mapItem.coordinate) + } + } + } + Item { + // Text with a duplicate for shadow. DropShadow as layer effect is kind of slow here. + y: mapItemImage.y + mapItemImage.height + visible: map.zoomLevel >= map.textVisibleZoom + Text { + id: mapItemTextShadow + x: mapItemText.x + 2; y: mapItemText.y + 2 + text: mapItemText.text + font.pointSize: mapItemText.font.pointSize + color: "black" + } + Text { + id: mapItemText + text: model.name + font.pointSize: 11.0 + color: mapHelper.model.selectedUuid === model.uuid ? "white" : "lightgrey" + } + } + } + } + } + + SequentialAnimation { + id: mapAnimationZoomIn + NumberAnimation { + target: map; property: "zoomLevel"; to: map.newZoomOut; duration: Math.abs(map.newZoomOut - map.zoomLevel) * 200 + } + ParallelAnimation { + CoordinateAnimation { target: map; property: "center"; to: map.newCenter; duration: 1000 } + NumberAnimation { + target: map; property: "zoomLevel"; to: map.newZoom ; duration: 2000; easing.type: Easing.InCubic + } + } + } + + ParallelAnimation { + id: mapAnimationZoomOut + NumberAnimation { target: map; property: "zoomLevel"; from: map.zoomLevel; to: map.newZoom; duration: 3000 } + SequentialAnimation { + PauseAnimation { duration: 2000 } + CoordinateAnimation { target: map; property: "center"; to: map.newCenter; duration: 2000 } + } + } + + ParallelAnimation { + id: mapAnimationClick + CoordinateAnimation { target: map; property: "center"; to: map.newCenter; duration: 500 } + NumberAnimation { target: map; property: "zoomLevel"; to: map.newZoom; duration: 500 } + } + + MouseArea { + anchors.fill: parent + onDoubleClicked: map.doubleClickHandler(map.toCoordinate(Qt.point(mouseX, mouseY))) + } + + function doubleClickHandler(coord) { + newCenter = coord + newZoom = zoomLevel + zoomStep + if (newZoom > maximumZoomLevel) + newZoom = maximumZoomLevel + mapAnimationClick.restart() + } + + function animateMapZoomOut() { + newCenter = defaultCenter + newZoom = defaultZoomOut + mapAnimationZoomIn.stop() + mapAnimationZoomOut.restart() + } + + function pointIsVisible(pt) { + return !isNaN(pt.x) + } + + function stopZoomAnimations() { + mapAnimationZoomIn.stop() + mapAnimationZoomOut.stop() + } + + function centerOnCoordinate(coord) { + stopZoomAnimations() + if (coord.latitude === 0.0 && coord.longitude === 0.0) { + // Do nothing + } else { + var newZoomOutFound = false + var zoomStored = zoomLevel + newZoomOut = zoomLevel + newCenter = coord + while (zoomLevel > minimumZoomLevel) { + var pt = fromCoordinate(coord) + if (pointIsVisible(pt)) { + newZoomOut = zoomLevel + newZoomOutFound = true + break + } + zoomLevel-- + } + if (!newZoomOutFound) + newZoomOut = defaultZoomOut + zoomLevel = zoomStored + newZoom = zoomStored + mapAnimationZoomIn.restart() + mapAnimationZoomOut.stop() + } + } + + function centerOnRectangle(topLeft, bottomRight, centerRect) { + stopZoomAnimations() + if (newCenter.latitude === 0.0 && newCenter.longitude === 0.0) { + // Do nothing + } else { + var centerStored = QtPositioning.coordinate(center.latitude, center.longitude) + var zoomStored = zoomLevel + var newZoomOutFound = false + newCenter = centerRect + // calculate zoom out + newZoomOut = zoomLevel + while (zoomLevel > minimumZoomLevel) { + var ptCenter = fromCoordinate(centerStored) + var ptCenterRect = fromCoordinate(centerRect) + if (pointIsVisible(ptCenter) && pointIsVisible(ptCenterRect)) { + newZoomOut = zoomLevel + newZoomOutFound = true + break + } + zoomLevel-- + } + if (!newZoomOutFound) + newZoomOut = defaultZoomOut + // calculate zoom in + center = newCenter + zoomLevel = maximumZoomLevel + var diagonalRect = topLeft.distanceTo(bottomRight) + while (zoomLevel > minimumZoomLevel) { + var c0 = toCoordinate(Qt.point(0.0, 0.0)) + var c1 = toCoordinate(Qt.point(width, height)) + if (c0.distanceTo(c1) > diagonalRect) { + newZoom = zoomLevel - 2.0 + break + } + zoomLevel-- + } + if (newZoom > defaultZoomIn) + newZoom = defaultZoomIn + zoomLevel = zoomStored + center = centerStored + mapAnimationZoomIn.restart() + mapAnimationZoomOut.stop() + } + } + + function deselectMapLocation() { + stopZoomAnimations() + } + } + + Rectangle { + id: editMessage + radius: padding + color: "#b08000" + border.color: "white" + x: (map.width - width) * 0.5; y: padding + width: editMessageText.width + padding * 2.0 + height: editMessageText.height + padding * 2.0 + visible: false + opacity: 0.0 + property int isVisible: -1 + property real padding: 10.0 + onOpacityChanged: visible = opacity != 0.0 + states: [ + State { when: editMessage.isVisible === 1; PropertyChanges { target: editMessage; opacity: 1.0 }}, + State { when: editMessage.isVisible === 0; PropertyChanges { target: editMessage; opacity: 0.0 }} + ] + transitions: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad }} + Text { + id: editMessageText + y: editMessage.padding; x: editMessage.padding + verticalAlignment: Text.AlignVCenter + color: "white" + font.pointSize: 11.0 + text: qsTr("Drag the selected dive location") + } + } + + Image { + id: toggleImage + x: 10; y: x + width: 40 + height: 40 + source: "qrc:///mapwidget-toggle-" + (map.activeMapType === map.mapType.SATELLITE ? "street" : "satellite") + SequentialAnimation { + id: toggleImageAnimation + PropertyAnimation { target: toggleImage; property: "scale"; from: 1.0; to: 0.8; duration: 120 } + PropertyAnimation { target: toggleImage; property: "scale"; from: 0.8; to: 1.0; duration: 80 } + } + MouseArea { + anchors.fill: parent + onClicked: { + map.activeMapType = map.activeMapType === map.mapType.SATELLITE ? map.mapType.STREET : map.mapType.SATELLITE + toggleImageAnimation.restart() + } + } + } + + Image { + id: imageZoomIn + x: 10 + (toggleImage.width - imageZoomIn.width) * 0.5; y: toggleImage.y + toggleImage.height + 10 + width: 20 + height: 20 + source: "qrc:///mapwidget-zoom-in" + SequentialAnimation { + id: imageZoomInAnimation + PropertyAnimation { target: imageZoomIn; property: "scale"; from: 1.0; to: 0.8; duration: 120 } + PropertyAnimation { target: imageZoomIn; property: "scale"; from: 0.8; to: 1.0; duration: 80 } + } + MouseArea { + anchors.fill: parent + onClicked: { + map.newCenter = map.center + map.newZoom = map.zoomLevel + map.zoomStep + if (map.newZoom > map.maximumZoomLevel) + map.newZoom = map.maximumZoomLevel + mapAnimationClick.restart() + imageZoomInAnimation.restart() + } + } + } + + Image { + id: imageZoomOut + x: imageZoomIn.x; y: imageZoomIn.y + imageZoomIn.height + 10 + source: "qrc:///mapwidget-zoom-out" + width: 20 + height: 20 + SequentialAnimation { + id: imageZoomOutAnimation + PropertyAnimation { target: imageZoomOut; property: "scale"; from: 1.0; to: 0.8; duration: 120 } + PropertyAnimation { target: imageZoomOut; property: "scale"; from: 0.8; to: 1.0; duration: 80 } + } + MouseArea { + anchors.fill: parent + onClicked: { + map.newCenter = map.center + map.newZoom = map.zoomLevel - map.zoomStep + mapAnimationClick.restart() + imageZoomOutAnimation.restart() + } + } + } + + function openLocationInGoogleMaps(latitude, longitude) { + var loc = latitude + " " + longitude + var url = "https://www.google.com/maps/place/" + loc + "/@" + loc + ",5000m/data=!3m1!1e3!4m2!3m1!1s0x0:0x0" + Qt.openUrlExternally(url) + } + + MapWidgetContextMenu { + id: contextMenu + y: 10; x: map.width - y + onActionSelected: { + switch (action) { + case contextMenu.actions.OPEN_LOCATION_IN_GOOGLE_MAPS: + openLocationInGoogleMaps(map.center.latitude, map.center.longitude) + break + case contextMenu.actions.COPY_LOCATION_DECIMAL: + mapHelper.copyToClipboardCoordinates(map.center, false) + break + case contextMenu.actions.COPY_LOCATION_SEXAGESIMAL: + mapHelper.copyToClipboardCoordinates(map.center, true) + break + case contextMenu.actions.SELECT_VISIBLE_LOCATIONS: + mapHelper.selectVisibleLocations() + break + } + } + } +} diff --git a/map-widget/qml/MapWidgetContextMenu.qml b/map-widget/qml/MapWidgetContextMenu.qml new file mode 100644 index 000000000..17450a729 --- /dev/null +++ b/map-widget/qml/MapWidgetContextMenu.qml @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0 +import QtQuick 2.0 + +Item { + id: container + signal actionSelected(int action) + + readonly property var actions: { + "OPEN_LOCATION_IN_GOOGLE_MAPS": 0, + "COPY_LOCATION_DECIMAL": 1, + "COPY_LOCATION_SEXAGESIMAL": 2, + "SELECT_VISIBLE_LOCATIONS": 3 + } + readonly property var menuItemData: [ + { idx: actions.OPEN_LOCATION_IN_GOOGLE_MAPS, itemText: qsTr("Open location in Google Maps") }, + { idx: actions.COPY_LOCATION_DECIMAL, itemText: qsTr("Copy location to clipboard (decimal)") }, + { idx: actions.COPY_LOCATION_SEXAGESIMAL, itemText: qsTr("Copy location to clipboard (sexagesimal)") }, + { idx: actions.SELECT_VISIBLE_LOCATIONS, itemText: qsTr("Select visible dive locations") } + ] + readonly property real itemTextPadding: 10.0 + readonly property real itemHeight: 34.0 + readonly property int itemAnimationDuration: 100 + readonly property color colorItemBackground: "#dedede" + readonly property color colorItemBackgroundSelected: "grey" + readonly property color colorItemText: "black" + readonly property color colorItemTextSelected: "#dedede" + readonly property color colorItemBorder: "black" + property int listViewIsVisible: -1 + property real maxItemWidth: 0.0 + + Image { + id: contextMenuImage + x: -width + source: "qrc:///mapwidget-context-menu" + + SequentialAnimation { + id:contextMenuImageAnimation + PropertyAnimation { target: contextMenuImage; property: "scale"; from: 1.0; to: 0.8; duration: 80 } + PropertyAnimation { target: contextMenuImage; property: "scale"; from: 0.8; to: 1.0; duration: 60 } + } + + MouseArea { + anchors.fill: parent + onClicked: { + contextMenuImageAnimation.restart() + listViewIsVisible = (listViewIsVisible !== 1) ? 1 : 0 + } + } + } + + ListModel { + id: listModel + property int selectedIdx: -1 + Component.onCompleted: { + for (var i = 0; i < menuItemData.length; i++) + append(menuItemData[i]); + } + } + + Component { + id: listItemDelegate + Rectangle { + color: model.idx === listModel.selectedIdx ? colorItemBackgroundSelected : colorItemBackground + width: maxItemWidth + height: itemHeight + border.color: colorItemBorder + Text { + x: itemTextPadding + height: itemHeight + verticalAlignment: Text.AlignVCenter + text: model.itemText + font.pointSize: 10.0 + color: model.idx === listModel.selectedIdx ? colorItemTextSelected : colorItemText + onWidthChanged: { + if (width + itemTextPadding * 2.0 > maxItemWidth) + maxItemWidth = width + itemTextPadding * 2.0 + } + Behavior on color { ColorAnimation { duration: itemAnimationDuration }} + } + Behavior on color { ColorAnimation { duration: itemAnimationDuration }} + } + } + + ListView { + id: listView + y: contextMenuImage.y + contextMenuImage.height + 10; + width: maxItemWidth; + height: listModel.count * itemHeight + visible: false + opacity: 0.0 + interactive: false + model: listModel + delegate: listItemDelegate + + onCountChanged: x = -maxItemWidth + onVisibleChanged: listModel.selectedIdx = -1 + onOpacityChanged: visible = opacity != 0.0 + + Timer { + id: timerListViewVisible + running: false + repeat: false + interval: itemAnimationDuration + 50 + onTriggered: listViewIsVisible = 0 + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (opacity < 1.0) + return; + var idx = listView.indexAt(mouseX, mouseY) + listModel.selectedIdx = idx + container.actionSelected(idx) + timerListViewVisible.restart() + } + } + states: [ + State { when: listViewIsVisible === 1; PropertyChanges { target: listView; opacity: 1.0 }}, + State { when: listViewIsVisible === 0; PropertyChanges { target: listView; opacity: 0.0 }} + ] + transitions: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad }} + } +} diff --git a/map-widget/qml/MapWidgetError.qml b/map-widget/qml/MapWidgetError.qml new file mode 100644 index 000000000..346e95f07 --- /dev/null +++ b/map-widget/qml/MapWidgetError.qml @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +import QtQuick 2.0 + +Item { + Text { + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "red" + text: qsTr("MapWidget.qml failed to load! +The QML modules QtPositioning and QtLocation could be missing!") + } +} diff --git a/map-widget/qml/icons/mapwidget-context-menu.png b/map-widget/qml/icons/mapwidget-context-menu.png Binary files differnew file mode 100644 index 000000000..6ab7cf77d --- /dev/null +++ b/map-widget/qml/icons/mapwidget-context-menu.png diff --git a/map-widget/qml/icons/mapwidget-marker-gray.png b/map-widget/qml/icons/mapwidget-marker-gray.png Binary files differnew file mode 100644 index 000000000..856db9f5b --- /dev/null +++ b/map-widget/qml/icons/mapwidget-marker-gray.png diff --git a/map-widget/qml/icons/mapwidget-marker-selected.png b/map-widget/qml/icons/mapwidget-marker-selected.png Binary files differnew file mode 100644 index 000000000..57f4efa27 --- /dev/null +++ b/map-widget/qml/icons/mapwidget-marker-selected.png diff --git a/map-widget/qml/icons/mapwidget-marker.png b/map-widget/qml/icons/mapwidget-marker.png Binary files differnew file mode 100644 index 000000000..a1be73866 --- /dev/null +++ b/map-widget/qml/icons/mapwidget-marker.png diff --git a/map-widget/qml/icons/mapwidget-toggle-satellite.png b/map-widget/qml/icons/mapwidget-toggle-satellite.png Binary files differnew file mode 100644 index 000000000..7ee536929 --- /dev/null +++ b/map-widget/qml/icons/mapwidget-toggle-satellite.png diff --git a/map-widget/qml/icons/mapwidget-toggle-street.png b/map-widget/qml/icons/mapwidget-toggle-street.png Binary files differnew file mode 100644 index 000000000..04a668c3f --- /dev/null +++ b/map-widget/qml/icons/mapwidget-toggle-street.png diff --git a/map-widget/qml/icons/mapwidget-zoom-in.png b/map-widget/qml/icons/mapwidget-zoom-in.png Binary files differnew file mode 100644 index 000000000..8c2521c3e --- /dev/null +++ b/map-widget/qml/icons/mapwidget-zoom-in.png diff --git a/map-widget/qml/icons/mapwidget-zoom-out.png b/map-widget/qml/icons/mapwidget-zoom-out.png Binary files differnew file mode 100644 index 000000000..bd372f17d --- /dev/null +++ b/map-widget/qml/icons/mapwidget-zoom-out.png |