[PATCH 2/3] QML UI: add planner page

Rick Walsh rickmwalsh at gmail.com
Wed Feb 10 02:33:33 PST 2016


This page provides a basic planner input and output page with editable
ListView items for cylinders, waypoints and deco stops.

It is not yet connected to the core planner c++ and c code. The ListModels
should be replaced with Qt models, sharings as much as possible with existing
models.

Signed-off-by: Rick Walsh <rickmwalsh at gmail.com>
---
 qt-mobile/qml/Planner.qml          | 476 +++++++++++++++++++++++++++++++++++++
 qt-mobile/qml/mobile-resources.qrc |   1 +
 2 files changed, 477 insertions(+)
 create mode 100644 qt-mobile/qml/Planner.qml

diff --git a/qt-mobile/qml/Planner.qml b/qt-mobile/qml/Planner.qml
new file mode 100644
index 0000000..212218f
--- /dev/null
+++ b/qt-mobile/qml/Planner.qml
@@ -0,0 +1,476 @@
+import QtQuick 2.4
+import QtQuick.Controls 1.2
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.2
+import QtQuick.Dialogs 1.2
+import org.kde.plasma.mobilecomponents 0.2 as MobileComponents
+import org.subsurfacedivelog.mobile 1.0
+
+MobileComponents.Page {
+	id: planner
+	objectName: "DivePlanner"
+	color: MobileComponents.Theme.viewBackgroundColor
+
+	property double cylinderSizeNew
+	property double cylinderWorkingPressureNew
+	property string cylinderDescriptionNew
+	property double pcO2New
+	property double pcHeNew
+	property string pressureStartNew
+	property double pressureEndNew
+	property double gasDepthNew
+	property bool newCylinder
+	property int cylinderIndex
+	property int stdColWidth: content.width * 0.2
+	property int horizontalPadding: MobileComponents.Units.smallSpacing
+	property int labelRowHeight: MobileComponents.Units.gridUnit * 1.6
+	property int inputRowHeight: MobileComponents.Units.gridUnit * 1.8
+
+
+	ScrollView {
+		anchors.fill: parent
+		Flickable {
+			id: flick
+			anchors.fill: parent
+			contentHeight: content.height
+			interactive: contentHeight > height
+			clip: true
+
+			Column {
+				id: content
+				anchors {
+					left: parent.left
+					leftMargin: MobileComponents.Units.gridUnit * 0.5
+					right: parent.right
+				}
+
+				MobileComponents.Heading {
+					text: "Dive planner"
+					level: 2
+				}
+
+				ListView {
+					id: inputCylinders
+					anchors {
+						left: parent.left
+						right: parent.right
+					}
+					height: headerItem.height + footerItem.height + labelRowHeight * Math.min(inputCylinders.count, 3)
+					model: gasModel
+					currentIndex: -1
+					delegate: cylinderListDelegate
+					boundsBehavior: Flickable.StopAtBounds
+					maximumFlickVelocity: parent.height * 5
+					cacheBuffer: parent.height * 5
+					focus: true
+					clip: true
+					header: MobileComponents.Heading {
+						height: paintedHeight + MobileComponents.Units.gridUnit / 2
+						verticalAlignment: Text.AlignBottom
+						text: "Cylinders and gases"
+						level: 4
+					}
+					footer: Button {
+						text: "Add cylinder"
+						action: addCylinder
+					}
+				}
+
+				ListView {
+					id: inputWaypoints
+					anchors {
+						left: parent.left
+						right: parent.right
+					}
+					height: headerItem.height + inputRowHeight * inputPointsModel.count + MobileComponents.Units.smallSpacing
+					model: inputPointsModel
+					currentIndex: -1
+					delegate: waypointsDelegate
+					boundsBehavior: Flickable.StopAtBounds
+					maximumFlickVelocity: parent.height * 5
+					interactive: false //flickable unnecessary as listview resizes to fit. Disabling allows scrolling the whole page
+					cacheBuffer: parent.height * 5
+					focus: true
+					clip: true
+
+					header: Column {
+						height: childrenRect.height
+						MobileComponents.Heading {
+							height: paintedHeight + MobileComponents.Units.gridUnit / 2
+							Layout.bottomMargin: MobileComponents.Units.largeSpacing / 2
+							verticalAlignment: Text.AlignTop
+							text: "Waypoints"
+							level: 4
+						}
+						Row {
+							anchors {
+								left: parent.left
+								leftMargin: horizontalPadding * 2
+								right: parent.right
+								rightMargin: horizontalPadding
+							}
+							Text {
+								text: 'Depth'
+								horizontalAlignment: Text.AlignHCenter
+								width: stdColWidth
+								opacity: 0.6
+								font.pointSize: subsurfaceTheme.smallPointSize
+							}
+							Text {
+								text: 'Duration'
+								horizontalAlignment: Text.AlignHCenter
+								width: stdColWidth
+								opacity: 0.6
+								font.pointSize: subsurfaceTheme.smallPointSize
+							}
+							Text {
+								text: 'Runtime'
+								horizontalAlignment: Text.AlignHCenter
+								width: stdColWidth
+								opacity: 0.6
+								font.pointSize: subsurfaceTheme.smallPointSize
+							}
+							Text {
+								text: 'Gas'
+								opacity: 0.6
+								font.pointSize: subsurfaceTheme.smallPointSize
+							}
+						}
+					}
+				}
+				ListView {
+					id: decoStops
+					anchors {
+						left: parent.left
+						right: parent.right
+					}
+					height: headerItem.height + inputRowHeight * Math.max(decoStopsModel.count, 8)
+					model: decoStopsModel
+					currentIndex: -1
+					delegate: decoStopsDelegate
+					boundsBehavior: Flickable.StopAtBounds
+					maximumFlickVelocity: parent.height * 5
+					interactive: false //flickable unnecessary as listview resizes to fit. Disabling allows scrolling the whole page
+					cacheBuffer: parent.height * 5
+					focus: true
+					clip: true
+
+					header: Column {
+						height: childrenRect.height
+						MobileComponents.Heading {
+							height: paintedHeight + MobileComponents.Units.gridUnit / 2
+							Layout.bottomMargin: MobileComponents.Units.largeSpacing / 2
+							verticalAlignment: Text.AlignTop
+							text: "Decompression stops"
+							level: 4
+						}
+						Row {
+							anchors {
+								left: parent.left
+								leftMargin: horizontalPadding * 2
+								right: parent.right
+								rightMargin: horizontalPadding
+							}
+							Text {
+								text: 'Depth'
+								horizontalAlignment: Text.AlignHCenter
+								width: stdColWidth
+								opacity: 0.6
+								font.pointSize: subsurfaceTheme.smallPointSize
+							}
+							Text {
+								text: 'Duration'
+								horizontalAlignment: Text.AlignHCenter
+								width: stdColWidth
+								opacity: 0.6
+								font.pointSize: subsurfaceTheme.smallPointSize
+							}
+							Text {
+								text: 'Runtime'
+								horizontalAlignment: Text.AlignHCenter
+								width: stdColWidth
+								opacity: 0.6
+								font.pointSize: subsurfaceTheme.smallPointSize
+							}
+							Text {
+								text: 'Gas'
+								opacity: 0.6
+								font.pointSize: subsurfaceTheme.smallPointSize
+							}
+						}
+					}
+				}
+				Item {
+					width: parent.width
+					Layout.fillHeight: true
+				}
+			}
+		}
+	}
+
+// TODO use Qt models instead so we can interact directly with core planner code
+	ListModel {
+		id: gasModel
+
+		ListElement {
+			cylinderType: "D12.2 232 bar"
+			cylinderSizeMl: 24400
+			gasName: "air"
+			gasDepthMm: 66000
+			o2Pm: 210
+			hePm: 0
+		}
+	}
+	ListModel {
+		id: inputPointsModel
+		ListElement {
+			property double depth: 15000
+			property int runtime: 1200
+			property int gasIndex: 0
+		}
+	}
+
+// Dummy model for the time being
+	ListModel {
+		id: decoStopsModel
+		ListElement {
+			property double depth: 15000
+			property int runtime: 3000
+			property int gasIndex: 0
+		}
+	}
+	Component {
+		id: cylinderListDelegate
+		MobileComponents.ListItemWithActions {
+			enabled: true
+			width: parent.width
+
+			Item {
+				width: parent.width - MobileComponents.Units.gridUnit
+				height: labelRowHeight - MobileComponents.Units.smallSpacing * 2
+
+				Row {
+					anchors {
+						left: parent.left
+						leftMargin: horizontalPadding
+						right: parent.right
+						rightMargin: horizontalPadding
+					}
+					MobileComponents.Label {
+						text: cylinderType
+						width: stdColWidth * 1.5
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+					MobileComponents.Label {
+						text: 'Gas: '
+						width: stdColWidth * 0.4
+						Layout.alignment: Qt.AlignRight
+						opacity: 0.6
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+					MobileComponents.Label {
+						text: gasName
+						width: stdColWidth * 0.8
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+					MobileComponents.Label {
+						text: 'Switch at: '
+						width: stdColWidth
+						Layout.alignment: Qt.AlignRight
+						opacity: 0.6
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+					MobileComponents.Label {
+						text: 0.001 * gasDepthMm + "m"
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+				}
+			}
+			function cylinderEdit() {
+				cylinderIndex = model.index
+				newCylinder = false
+				cylinderDetailsWindow.isPlanner = true
+				cylinderDetailsWindow.cylinderDescriptionText = gasModel.get(cylinderIndex).cylinderType
+				cylinderDetailsWindow.cylinderSizeText = 0.001 * gasModel.get(cylinderIndex).cylinderSizeMl
+				cylinderDetailsWindow.pcO2Text = 0.1 * gasModel.get(cylinderIndex).o2Pm
+				cylinderDetailsWindow.pcHeText = 0.1 * gasModel.get(cylinderIndex).hePm
+				cylinderDetailsWindow.setGas(gasModel.get(cylinderIndex).cylinderType)
+				stackView.push(cylinderDetailsWindow)
+			}
+
+			onClicked: {
+				cylinderEdit()
+			}
+			actions: [
+				Action {
+					iconName: "trash-empty"
+					onTriggered: {
+						gasModel.remove(model.index)
+					}
+				},
+				Action {
+					iconName: "document-edit"
+					onTriggered: {
+						cylinderEdit()
+					}
+				}
+
+			]
+		}
+	}
+
+// TODO: we shouldn't permit negative durations - either sort model by runtime (in c++) or have data validation
+	Component {
+		id: waypointsDelegate
+		MobileComponents.ListItemWithActions {
+			enabled: true
+			width: parent.width
+
+			Item {
+				width: parent.width - MobileComponents.Units.gridUnit
+				height: inputRowHeight - MobileComponents.Units.smallSpacing * 2
+
+				Row {
+					anchors {
+						left: parent.left
+						leftMargin: horizontalPadding
+						right: parent.right
+						rightMargin: horizontalPadding
+					}
+					TextField {
+						id: txtDepth
+						inputMethodHints: Qt.ImhDigitsOnly
+						validator: DoubleValidator {bottom: 0; top: 200; decimals:0; notation: DoubleValidator.StandardNotation}
+						horizontalAlignment: TextInput.AlignHCenter
+						width: stdColWidth
+						font.pointSize: subsurfaceTheme.smallPointSize
+						onEditingFinished: {
+							inputPointsModel.set(model.index, {depth: 1000 * Number(text)})
+							if (model.index == inputPointsModel.count - 1)
+								inputPointsModel.append({})
+						}
+					}
+					MobileComponents.Label {
+						id: txtDuration
+						text: {
+							if (inputPointsModel.get(model.index).runtime > 0) {
+								if (model.index == 0)
+									return inputPointsModel.get(model.index).runtime / 60
+								else if (inputPointsModel.get(model.index - 1).runtime > 0)
+									return (inputPointsModel.get(model.index).runtime
+										- inputPointsModel.get(model.index - 1).runtime) / 60
+							} else {
+							return undefined
+							}
+						}
+						horizontalAlignment: Text.AlignHCenter
+						width: stdColWidth
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+					TextField {
+						id: txtRuntime
+						inputMethodHints: Qt.ImhDigitsOnly
+						validator: DoubleValidator {bottom: 0; top: 200; decimals:0; notation: DoubleValidator.StandardNotation}
+						horizontalAlignment: TextInput.AlignHCenter
+						width: stdColWidth
+						font.pointSize: subsurfaceTheme.smallPointSize
+						onEditingFinished: {
+							inputPointsModel.set(model.index, {runtime: 60 * Number(text)})
+							if (model.index == inputPointsModel.count - 1)
+								inputPointsModel.append({})
+						}
+					}
+					MobileComponents.Label {
+						text: "Gas"
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+				}
+			}
+			actions: [
+				Action {
+					iconName: "trash-empty"
+					onTriggered: {
+						print("delete waypoint")
+						inputPointsModel.remove(model.index)
+					}
+				}
+			]
+		}
+	}
+	Component {
+		id: decoStopsDelegate
+		MobileComponents.ListItem {
+			enabled: true
+			width: parent.width
+
+			Item {
+				width: parent.width - MobileComponents.Units.gridUnit
+				height: labelRowHeight - MobileComponents.Units.smallSpacing * 2
+
+				Row {
+					anchors {
+						left: parent.left
+						leftMargin: horizontalPadding
+						right: parent.right
+						rightMargin: horizontalPadding
+					}
+					MobileComponents.Label {
+						id: txtDecoDepth
+						text: depth * 0.001
+						horizontalAlignment: TextInput.AlignHCenter
+						width: stdColWidth
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+					MobileComponents.Label {
+						id: txtDuration
+						horizontalAlignment: Text.AlignHCenter
+						width: stdColWidth
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+					MobileComponents.Label {
+						id: txtDecoRuntime
+						text: runtime / 60
+						horizontalAlignment: TextInput.AlignHCenter
+						width: stdColWidth
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+					MobileComponents.Label {
+						text: gasModel.get(gasIndex).gasName
+						font.pointSize: subsurfaceTheme.smallPointSize
+					}
+				}
+			}
+		}
+	}
+	Action {
+		id: addCylinder
+		onTriggered: {
+			newCylinder = true
+			cylinderDetailsWindow.isPlanner = true
+			stackView.push(cylinderDetailsWindow)
+		}
+	}
+	CylinderDetailsEdit {
+		id: cylinderDetailsWindow
+		visible: false
+		onCylinderOk: {
+			if (newCylinder) {
+				gasModel.append({
+					cylinderType: cylinderDetailsWindow.cylinderDescriptionText,
+					o2Pm: cylinderDetailsWindow.o2Pm,
+					hePm: cylinderDetailsWindow.hePm,
+					gasName: cylinderDetailsWindow.gasNameText,
+					gasDepthMm: Number(cylinderDetailsWindow.gasDepthMm)
+				})
+			} else {
+				gasModel.set(cylinderIndex, {
+					cylinderType: cylinderDetailsWindow.cylinderDescriptionText,
+					o2Pm: cylinderDetailsWindow.o2Pm,
+					hePm: cylinderDetailsWindow.hePm,
+					gasName: cylinderDetailsWindow.gasNameText,
+					gasDepthMm: Number(cylinderDetailsWindow.gasDepthMm)
+				})
+			}
+		}
+	}
+}
diff --git a/qt-mobile/qml/mobile-resources.qrc b/qt-mobile/qml/mobile-resources.qrc
index 77ec4ff..eb8fdc5 100644
--- a/qt-mobile/qml/mobile-resources.qrc
+++ b/qt-mobile/qml/mobile-resources.qrc
@@ -9,6 +9,7 @@
 		<file>DiveDetailsEdit.qml</file>
 		<file>DiveDetailsView.qml</file>
 		<file>DownloadFromDiveComputer.qml</file>
+		<file>Planner.qml</file>
 		<file>CylinderDetailsEdit.qml</file>
 		<file>GpsList.qml</file>
 		<file>Log.qml</file>
-- 
2.5.0



More information about the subsurface mailing list