From 60d758083944e2cc7931c761c9d35e40319b3cb4 Mon Sep 17 00:00:00 2001
From: DoinLakeFlyer <don@thegagnes.com>
Date: Tue, 7 Apr 2020 10:44:39 -0700
Subject: [PATCH] New grid based values page editor support

---
 qgcresources.qrc                              |   2 +
 resources/LockClosed.svg                      | 136 ++++++
 resources/LockOpen.svg                        | 136 ++++++
 src/FlightMap/Widgets/ValuePageWidget.qml     | 373 +++++++++-------
 .../Widgets/ValuesWidgetController.cc         | 422 +++++++++++++++++-
 .../Widgets/ValuesWidgetController.h          | 122 ++++-
 src/QGCApplication.cc                         |   2 +
 src/QmlControls/PageView.qml                  |  14 +-
 src/QmlControls/QGCLabel.qml                  |   9 +-
 src/api/QGCCorePlugin.cc                      |  37 +-
 src/api/QGCCorePlugin.h                       |   5 +-
 11 files changed, 1038 insertions(+), 220 deletions(-)
 create mode 100644 resources/LockClosed.svg
 create mode 100644 resources/LockOpen.svg

diff --git a/qgcresources.qrc b/qgcresources.qrc
index 3b76854b2c..977a849a0d 100644
--- a/qgcresources.qrc
+++ b/qgcresources.qrc
@@ -25,6 +25,8 @@
         <file alias="JoystickBezel.png">resources/JoystickBezel.png</file>
         <file alias="JoystickBezelLight.png">resources/JoystickBezelLight.png</file>
         <file alias="land.svg">resources/land.svg</file>
+        <file alias="LockClosed.svg">resources/LockClosed.svg</file>
+        <file alias="LockOpen.svg">resources/LockOpen.svg</file>
         <file alias="notile.png">resources/notile.png</file>
         <file alias="Pause.svg">resources/Pause.svg</file>
         <file alias="pause-mission.svg">resources/pause-mission.svg</file>
diff --git a/resources/LockClosed.svg b/resources/LockClosed.svg
new file mode 100644
index 0000000000..600eb240ef
--- /dev/null
+++ b/resources/LockClosed.svg
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:cc="http://web.resource.org/cc/"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:ns1="http://sozi.baierouge.fr"
+    id="svg2064"
+    viewBox="0 0 150 150"
+    version="1.0"
+    y="0"
+    x="0"
+  >
+  <g
+      id="layer1"
+    >
+    <g
+        id="g2061"
+        transform="translate(10.641 12.191)"
+      >
+      <path
+          id="rect2723"
+          style="fill-rule:evenodd;fill:#282828"
+          d="m64.359 2c-20.072 0-36.231 16.159-36.231 36.231v40.256h10.064v-40.256c0-14.497 11.671-26.167 26.167-26.167s26.167 11.67 26.167 26.167v40.256h10.064v-40.256c0-20.072-16.159-36.231-36.231-36.231z"
+      />
+      <rect
+          id="rect1941"
+          style="fill-rule:evenodd;fill:#000000"
+          rx="0.5"
+          ry=".5"
+          height="66.259"
+          width="85.603"
+          y="57.359"
+          x="21.558"
+      />
+    </g
+    >
+  </g
+  >
+  <metadata
+    >
+    <rdf:RDF
+      >
+      <cc:Work
+        >
+        <dc:format
+          >image/svg+xml</dc:format
+        >
+        <dc:type
+            rdf:resource="http://purl.org/dc/dcmitype/StillImage"
+        />
+        <cc:license
+            rdf:resource="http://creativecommons.org/licenses/publicdomain/"
+        />
+        <dc:publisher
+          >
+          <cc:Agent
+              rdf:about="http://openclipart.org/"
+            >
+            <dc:title
+              >Openclipart</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:publisher
+        >
+        <dc:title
+          >padlock silhouette a.j.  01</dc:title
+        >
+        <dc:date
+          >2011-01-20T20:53:10</dc:date
+        >
+        <dc:description
+          >Originally uploaded by AJ Ashton for OCAL 0.18</dc:description
+        >
+        <dc:source
+          >https://openclipart.org/detail/105751/padlock-silhouette-a.j.--01-by-anonymous</dc:source
+        >
+        <dc:creator
+          >
+          <cc:Agent
+            >
+            <dc:title
+              >Anonymous</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:creator
+        >
+        <dc:subject
+          >
+          <rdf:Bag
+            >
+            <rdf:li
+              >fix</rdf:li
+            >
+            <rdf:li
+              >keyword</rdf:li
+            >
+            <rdf:li
+              >librarians</rdf:li
+            >
+            <rdf:li
+              >tag</rdf:li
+            >
+          </rdf:Bag
+          >
+        </dc:subject
+        >
+      </cc:Work
+      >
+      <cc:License
+          rdf:about="http://creativecommons.org/licenses/publicdomain/"
+        >
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Reproduction"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Distribution"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
+        />
+      </cc:License
+      >
+    </rdf:RDF
+    >
+  </metadata
+  >
+</svg
+>
diff --git a/resources/LockOpen.svg b/resources/LockOpen.svg
new file mode 100644
index 0000000000..1bed7f6cea
--- /dev/null
+++ b/resources/LockOpen.svg
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:cc="http://web.resource.org/cc/"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:ns1="http://sozi.baierouge.fr"
+    id="svg2064"
+    viewBox="0 0 150 150"
+    version="1.0"
+    y="0"
+    x="0"
+  >
+  <g
+      id="layer1"
+    >
+    <g
+        id="g2096"
+        transform="translate(-2.9648 2.5)"
+      >
+      <path
+          id="rect2723"
+          style="fill-rule:evenodd;fill:#282828"
+          d="m112.5 4.1909c-20.072 0-36.231 16.159-36.231 36.231v40.256h10.064v-40.256c0-14.497 11.671-26.167 26.167-26.167 14.5 0 26.17 11.67 26.17 26.167v27.756h10.06v-27.756c0-20.072-16.16-36.231-36.23-36.231z"
+      />
+      <rect
+          id="rect1941"
+          style="fill-rule:evenodd;fill:#000000"
+          rx="0.5"
+          ry=".5"
+          height="66.259"
+          width="85.603"
+          y="74.55"
+          x="7.1987"
+      />
+    </g
+    >
+  </g
+  >
+  <metadata
+    >
+    <rdf:RDF
+      >
+      <cc:Work
+        >
+        <dc:format
+          >image/svg+xml</dc:format
+        >
+        <dc:type
+            rdf:resource="http://purl.org/dc/dcmitype/StillImage"
+        />
+        <cc:license
+            rdf:resource="http://creativecommons.org/licenses/publicdomain/"
+        />
+        <dc:publisher
+          >
+          <cc:Agent
+              rdf:about="http://openclipart.org/"
+            >
+            <dc:title
+              >Openclipart</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:publisher
+        >
+        <dc:title
+          >padlock unlocked silhou 01</dc:title
+        >
+        <dc:date
+          >2011-01-20T20:53:10</dc:date
+        >
+        <dc:description
+          >Originally uploaded by AJ Ashton for OCAL 0.18</dc:description
+        >
+        <dc:source
+          >https://openclipart.org/detail/105745/padlock-unlocked-silhou-01-by-anonymous</dc:source
+        >
+        <dc:creator
+          >
+          <cc:Agent
+            >
+            <dc:title
+              >Anonymous</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:creator
+        >
+        <dc:subject
+          >
+          <rdf:Bag
+            >
+            <rdf:li
+              >fix</rdf:li
+            >
+            <rdf:li
+              >keyword</rdf:li
+            >
+            <rdf:li
+              >librarians</rdf:li
+            >
+            <rdf:li
+              >tag</rdf:li
+            >
+          </rdf:Bag
+          >
+        </dc:subject
+        >
+      </cc:Work
+      >
+      <cc:License
+          rdf:about="http://creativecommons.org/licenses/publicdomain/"
+        >
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Reproduction"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Distribution"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
+        />
+      </cc:License
+      >
+    </rdf:RDF
+    >
+  </metadata
+  >
+</svg
+>
diff --git a/src/FlightMap/Widgets/ValuePageWidget.qml b/src/FlightMap/Widgets/ValuePageWidget.qml
index 944f594425..e0db7f7527 100644
--- a/src/FlightMap/Widgets/ValuePageWidget.qml
+++ b/src/FlightMap/Widgets/ValuePageWidget.qml
@@ -10,6 +10,8 @@
 import QtQuick          2.3
 import QtQuick.Dialogs  1.2
 import QtQuick.Layouts  1.2
+import QtQuick.Controls 2.5
+import QtQml            2.12
 
 import QGroundControl.Controls      1.0
 import QGroundControl.ScreenTools   1.0
@@ -21,23 +23,32 @@ import QGroundControl               1.0
 
 /// Value page for InstrumentPanel PageView
 Column {
-    id:         _largeColumn
+    id:         _root
     width:      pageWidth
-    spacing:    _margins
+    spacing:    ScreenTools.defaultFontPixelHeight / 2
 
     property bool showSettingsIcon: true
-
-    property var    _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle ? QGroundControl.multiVehicleManager.activeVehicle : QGroundControl.multiVehicleManager.offlineEditingVehicle
-    property real   _margins:       ScreenTools.defaultFontPixelWidth / 2
+    property bool showLockIcon:     true
+
+    property var    _activeVehicle:                 QGroundControl.multiVehicleManager.activeVehicle ? QGroundControl.multiVehicleManager.activeVehicle : QGroundControl.multiVehicleManager.offlineEditingVehicle
+    property real   _margins:                       ScreenTools.defaultFontPixelWidth / 2
+    property int    _colMax:                        4
+    property bool   _settingsUnlocked:              false
+    property var    _valuePickerInstrumentValue:    null
+    property int    _valuePickerRowIndex:           0
+    property var    _rgFontSizes:                   [ ScreenTools.defaultFontPointSize, ScreenTools.smallFontPointSize, ScreenTools.mediumFontPointSize, ScreenTools.largeFontPointSize ]
+    property real   _blankEntryHeight:              ScreenTools.defaultFontPixelHeight * 2
+    property real   _columnButtonWidth:             ScreenTools.minTouchPixels / 2
+    property real   _columnButtonHeight:            ScreenTools.minTouchPixels
+    property real   _columnButtonSpacing:           2
+    property real   _columnButtonsTotalHeight:      (_columnButtonHeight * 2) + _columnButtonSpacing
 
     QGCPalette { id:qgcPal; colorGroupEnabled: true }
 
-    ValuesWidgetController {
-        id: controller
-    }
+    ValuesWidgetController { id: controller }
 
-    function showSettings() {
-        mainWindow.showComponentDialog(propertyPicker, qsTr("Value Widget Setup"), mainWindow.showDialogDefaultWidth, StandardButton.Ok)
+    function showSettings(settingsUnlocked) {
+        _settingsUnlocked = settingsUnlocked
     }
 
     function listContains(list, value) {
@@ -49,88 +60,166 @@ Column {
         return false
     }
 
-    Repeater {
-        model: _activeVehicle ? controller.largeValues : 0
-        Loader {
-            sourceComponent: fact ? largeValue : undefined
-            property Fact fact: _activeVehicle.getFact(modelData.replace("Vehicle.", ""))
-        }
-    } // Repeater - Large
-
-    Flow {
-        id:                 _smallFlow
-        width:              parent.width
-        layoutDirection:    Qt.LeftToRight
-        spacing:            _margins
-
-        Repeater {
-            model: _activeVehicle ? controller.smallValues : 0
-            Loader {
-                sourceComponent: fact ? smallValue : undefined
-                property Fact fact: _activeVehicle.getFact(modelData.replace("Vehicle.", ""))
-            }
-        } // Repeater - Small
-    } // Flow
+    ButtonGroup { id: factRadioGroup }
 
     Component {
-        id: largeValue
+        id: valueItemMouseAreaComponent
 
-        Column {
-            width:  _largeColumn.width
-            property bool largeValue: listContains(controller.altitudeProperties, fact.name)
-
-            QGCLabel {
-                width:                  parent.width
-                horizontalAlignment:    Text.AlignHCenter
-                wrapMode:               Text.WordWrap
-                text:                   fact.shortDescription + (fact.units ? " (" + fact.units + ")" : "")
-            }
-            QGCLabel {
-                width:                  parent.width
-                horizontalAlignment:    Text.AlignHCenter
-                font.pointSize:         ScreenTools.mediumFontPointSize * (largeValue ? 1.3 : 1.0)
-                font.family:            largeValue ? ScreenTools.demiboldFontFamily : ScreenTools.normalFontFamily
-                fontSizeMode:           Text.HorizontalFit
-                text:                   fact.enumOrValueString
+        MouseArea {
+            anchors.centerIn:   parent
+            width:              parent.width
+            height:             _columnButtonsTotalHeight
+            visible:            _settingsUnlocked
+
+            property var instrumentValue
+            property int rowIndex
+
+            onClicked: {
+                _valuePickerInstrumentValue = instrumentValue
+                _valuePickerRowIndex = rowIndex
+                mainWindow.showComponentDialog(valuePickerDialog, qsTr("Select Value"), mainWindow.showDialogDefaultWidth, StandardButton.Ok)
             }
         }
     }
 
-    Component {
-        id: smallValue
+    Repeater {
+        id:     rowRepeater
+        model:  controller.valuesModel
 
         Column {
-            width:  (pageWidth / 2) - (_margins / 2) - 0.1
-            clip:   true
-
-            QGCLabel {
-                width:                  parent.width
-                wrapMode:               Text.WordWrap
-                horizontalAlignment:    Text.AlignHCenter
-                font.pointSize:         ScreenTools.isTinyScreen ? ScreenTools.smallFontPointSize * 0.75 : ScreenTools.smallFontPointSize
-                text:                   fact.shortDescription
-            }
-            QGCLabel {
-                width:                  parent.width
-                horizontalAlignment:    Text.AlignHCenter
-                fontSizeMode:           Text.HorizontalFit
-                text:                   fact.enumOrValueString
-            }
-            QGCLabel {
-                width:                  parent.width
-                horizontalAlignment:    Text.AlignHCenter
-                font.pointSize:         ScreenTools.isTinyScreen ? ScreenTools.smallFontPointSize * 0.75 : ScreenTools.smallFontPointSize
-                fontSizeMode:           Text.HorizontalFit
-                text:                   fact.units
+            id:             rowRepeaterLayout
+            spacing:        1
+
+            property int rowIndex: index
+
+            Row {
+                id:         columnRow
+                spacing:    1
+
+                Repeater {
+                    id:     columnRepeater
+                    model:  object
+
+                    property real _interColumnSpacing:  (columnRepeater.count - (_settingsUnlocked ? 0 : 1)) * columnRow.spacing
+                    property real columnWidth:          (pageWidth - (_settingsUnlocked ? _columnButtonWidth : 0) - _interColumnSpacing) / columnRepeater.count
+
+                    onItemAdded: valueItemMouseAreaComponent.createObject(item, { "instrumentValue": object.get(index), "rowIndex": index })
+
+                    Item {
+                        width:                  columnRepeater.columnWidth
+                        height:                 value.y + value.height
+                        anchors.verticalCenter: _settingsUnlocked ? parent.verticalCenter : undefined
+                        anchors.bottom:         _settingsUnlocked ? undefined : parent.bottom
+
+                        QGCLabel {
+                            width:                  columnRepeater.columnWidth
+                            height:                 _columnButtonsTotalHeight
+                            font.pointSize:         ScreenTools.smallFontPointSize
+                            text:                   _settingsUnlocked ? qsTr("BLANK") : ""
+                            horizontalAlignment:    Text.AlignHCenter
+                            verticalAlignment:      Text.AlignVCenter
+                            visible:                !object.fact
+                        }
+
+                        QGCLabel {
+                            id:                     label
+                            width:                  columnRepeater.columnWidth
+                            font.pointSize:         ScreenTools.smallFontPointSize
+                            text:                   object.label.toUpperCase()
+                            horizontalAlignment:    Text.AlignHCenter
+                            visible:                object.fact && object.label
+                        }
+
+                        QGCLabel {
+                            id:                     value
+                            anchors.topMargin:      label.visible ? 2 : 0
+                            anchors.top:            label.visible ? label.bottom : parent.top
+                            width:                  columnRepeater.columnWidth
+                            font.pointSize:         _rgFontSizes[object.fontSize]
+                            text:                   visible ? (object.fact.enumOrValueString + (object.showUnits ? object.fact.units : "")) : ""
+                            horizontalAlignment:    Text.AlignHCenter
+                            visible:                object.fact
+                        }
+                    }
+                } // Repeater - columns
+
+                ColumnLayout {
+                    id:                 columnsButtonsLayout
+                    width:              _columnButtonWidth
+                    spacing:            _columnButtonSpacing
+                    visible:            _settingsUnlocked
+
+                    QGCButton {
+                        Layout.fillHeight:      true
+                        Layout.minimumHeight:   ScreenTools.minTouchPixels
+                        Layout.preferredWidth:  parent.width
+                        text:                   qsTr("+")
+                        onClicked:              controller.appendColumn(rowRepeaterLayout.rowIndex)
+                    }
+
+                    QGCButton {
+                        Layout.fillHeight:      true
+                        Layout.minimumHeight:   ScreenTools.minTouchPixels
+                        Layout.preferredWidth:  parent.width
+                        text:                   qsTr("-")
+                        enabled:                index !== 0 || columnRepeater.count !== 1
+                        onClicked:              controller.deleteLastColumn(rowRepeaterLayout.rowIndex)
+                    }
+                }
+            } // RowLayout
+
+            RowLayout {
+                width:      parent.width
+                height:             ScreenTools.defaultFontPixelWidth * 2
+                spacing:            1
+                visible:            _settingsUnlocked
+
+                QGCButton {
+                    Layout.fillWidth:   true
+                    Layout.preferredHeight: ScreenTools.defaultFontPixelWidth * 2
+                    text:               qsTr("+")
+                    onClicked:          controller.insertRow(index)
+                }
+
+                QGCButton {
+                    Layout.fillWidth:   true
+                    Layout.preferredHeight: ScreenTools.defaultFontPixelWidth * 2
+                    text:               qsTr("-")
+                    enabled:            index !== 0
+                    onClicked:          controller.deleteRow(index)
+                }
             }
         }
+    } // Repeater - rows
+
+    QGCButton {
+        anchors.left:   parent.left
+        anchors.right:  parent.right
+        text:           qsTr("Reset To Defaults")
+        visible:        _settingsUnlocked
+        onClicked:      controller.resetToDefaults()
     }
 
     Component {
-        id: propertyPicker
+        id: valuePickerDialog
 
         QGCViewDialog {
-            id: _propertyPickerDialog
+            function accept() {
+                if (factRadioGroup.checkedButton) {
+                    _valuePickerInstrumentValue.setFact(factRadioGroup.checkedButton.radioFactGroupName, factRadioGroup.checkedButton.radioFact.name, labelTextField.text, fontSizeCombo.currentIndex)
+                } else {
+                    _valuePickerInstrumentValue.clearFact()
+                }
+
+                hideDialog()
+            }
+
+            Connections {
+                target: factRadioGroup
+                onCheckedButtonChanged: labelTextField.text = factRadioGroup.checkedButton.radioFact.shortDescription
+            }
+
+            ButtonGroup { id: fontRadioGroup }
 
             QGCFlickable {
                 anchors.fill:       parent
@@ -138,39 +227,50 @@ Column {
                 flickableDirection: Flickable.VerticalFlick
                 clip:               true
 
-                Column {
+                ColumnLayout {
                     id:             column
                     anchors.left:   parent.left
                     anchors.right:  parent.right
                     spacing:        _margins
 
-                    /*
-                      Leaving this here for now just in case
-                    FactCheckBox {
-                        text:       qsTr("Show large compass")
-                        fact:       _showLargeCompass
-                        visible:    _showLargeCompass.visible
+                    RowLayout {
+                        Layout.fillWidth:   true
+                        spacing:            ScreenTools.defaultFontPixelWidth
+
+                        QGCLabel { text: qsTr("Label") }
+                        QGCTextField {
+                            id:                 labelTextField
+                            Layout.fillWidth:   true
+                            text:               _valuePickerInstrumentValue.label
+                        }
+                    }
 
-                        property Fact _showLargeCompass: QGroundControl.settingsManager.appSettings.showLargeCompass
+                    RowLayout {
+                        spacing: ScreenTools.defaultFontPixelWidth
+
+                        QGCLabel { text: qsTr("Font Size (for whole row)") }
+                        QGCComboBox {
+                            id:             fontSizeCombo
+                            model:          [ qsTr("Default"), qsTr("Small"), qsTr("Medium"), qsTr("Large") ]
+                            currentIndex:   _valuePickerInstrumentValue.fontSize
+                            sizeToContents: true
+                            onActivated:    _valuePickerInstrumentValue.fontSize = index
+                        }
                     }
-                    */
 
-                    Item {
-                        width:  1
-                        height: _margins
+                    QGCCheckBox {
+                        text:       qsTr("Show Units")
+                        checked:    _valuePickerInstrumentValue.showUnits
+                        onClicked:  _valuePickerInstrumentValue.showUnits = checked
                     }
 
-                    QGCLabel {
-                        id:     _label
-                        anchors.left:   parent.left
-                        anchors.right:  parent.right
-                        wrapMode:       Text.WordWrap
-                        text:   qsTr("Select the values you want to display:")
+                    QGCButton {
+                        text:       qsTr("Blank Entry")
+                        onClicked:  { _valuePickerInstrumentValue.clearFact(); hideDialog() }
                     }
 
                     Loader {
-                        anchors.left:       parent.left
-                        anchors.right:      parent.right
+                        Layout.fillWidth:   true
                         sourceComponent:    factGroupList
 
                         property var    factGroup:     _activeVehicle
@@ -181,14 +281,12 @@ Column {
                         model: _activeVehicle.factGroupNames
 
                         Loader {
-                            anchors.left:       parent.left
-                            anchors.right:      parent.right
+                            Layout.fillWidth:   true
                             sourceComponent:    factGroupList
 
                             property var    factGroup:     _activeVehicle.getFactGroup(modelData)
                             property string factGroupName: modelData
                         }
-
                     }
                 }
             }
@@ -203,8 +301,6 @@ Column {
         // property string factGroupName
 
         Column {
-            spacing:    _margins
-
             SectionHeader {
                 id:             header
                 anchors.left:   parent.left
@@ -214,78 +310,23 @@ Column {
             }
 
             Column {
-                spacing:    _margins
-                visible:    header.checked
+                visible: header.checked
 
                 Repeater {
                     model: factGroup ? factGroup.factNames : 0
 
-                    RowLayout {
-                        spacing: _margins
-                        visible: factGroup.getFact(modelData).shortDescription !== ""
-
-                        property string propertyName: factGroupName + "." + modelData
+                    QGCRadioButton {
+                        text:               radioFact.shortDescription
+                        ButtonGroup.group:  factRadioGroup
+                        checked:            radioFactGroupName == _valuePickerInstrumentValue.factGroupName && radioFact == _valuePickerInstrumentValue.fact
 
-                        function removeFromList(list, value) {
-                            var newList = []
-                            for (var i=0; i<list.length; i++) {
-                                if (list[i] !== value) {
-                                    newList.push(list[i])
-                                }
-                            }
-                            return newList
-                        }
+                        property string radioFactGroupName: factGroupName
+                        property var    radioFact:          factGroup.getFact(modelData)
 
-                        function addToList(list, value) {
-                            var found = false
-                            for (var i=0; i<list.length; i++) {
-                                if (list[i] === value) {
-                                    found = true
-                                    break
-                                }
+                        Component.onCompleted: {
+                            if (checked) {
+                                header.checked = true
                             }
-                            if (!found) {
-                                list.push(value)
-                            }
-                            return list
-                        }
-
-                        function updateValues() {
-                            if (_addCheckBox.checked) {
-                                if (_largeCheckBox.checked) {
-                                    controller.largeValues = addToList(controller.largeValues, propertyName)
-                                    controller.smallValues = removeFromList(controller.smallValues, propertyName)
-                                } else {
-                                    controller.smallValues = addToList(controller.smallValues, propertyName)
-                                    controller.largeValues = removeFromList(controller.largeValues, propertyName)
-                                }
-                            } else {
-                                controller.largeValues = removeFromList(controller.largeValues, propertyName)
-                                controller.smallValues = removeFromList(controller.smallValues, propertyName)
-                            }
-                        }
-
-                        QGCCheckBox {
-                            id:                     _addCheckBox
-                            text:                   factGroup.getFact(modelData).shortDescription
-                            checked:                listContains(controller.smallValues, propertyName) || _largeCheckBox.checked
-                            onClicked:              updateValues()
-                            Layout.fillWidth:       true
-                            Layout.minimumWidth:    ScreenTools.defaultFontPixelWidth * 20
-
-                            Component.onCompleted: {
-                                if (checked) {
-                                    header.checked = true
-                                }
-                            }
-                        }
-
-                        QGCCheckBox {
-                            id:                     _largeCheckBox
-                            text:                   qsTr("Large")
-                            checked:                listContains(controller.largeValues, propertyName)
-                            enabled:                _addCheckBox.checked
-                            onClicked:              updateValues()
                         }
                     }
                 }
diff --git a/src/FlightMap/Widgets/ValuesWidgetController.cc b/src/FlightMap/Widgets/ValuesWidgetController.cc
index b5fe6580be..077b798a12 100644
--- a/src/FlightMap/Widgets/ValuesWidgetController.cc
+++ b/src/FlightMap/Widgets/ValuesWidgetController.cc
@@ -14,51 +14,429 @@
 
 #include <QSettings>
 
-const char* ValuesWidgetController::_groupKey =         "ValuesWidget";
-const char* ValuesWidgetController::_largeValuesKey =   "large";
-const char* ValuesWidgetController::_smallValuesKey =   "small";
+const char* ValuesWidgetController::_groupKey =         "ValuesWidget2";
+const char* ValuesWidgetController::_rowsKey =          "rows";
+const char* ValuesWidgetController::_columnsKey =       "columns";
 
-ValuesWidgetController::ValuesWidgetController(void)
+const char* ValuesWidgetController::_deprecatedGroupKey =         "ValuesWidget";
+const char* ValuesWidgetController::_deprecatedLargeValuesKey =   "large";
+const char* ValuesWidgetController::_deprecatedSmallValuesKey =   "small";
+
+const char*  InstrumentValue::_factGroupNameKey =       "groupName";
+const char*  InstrumentValue::_factNameKey =             "factName";
+const char*  InstrumentValue::_labelKey =               "label";
+const char*  InstrumentValue::_fontSizeKey =            "fontSize";
+const char*  InstrumentValue::_showUnitsKey =           "showUnits";
+
+ValuesWidgetController::ValuesWidgetController(bool forDefaultSettingsCreation)
+    : _valuesModel(new QmlObjectListModel(this))
 {
     QSettings settings;
 
     settings.beginGroup(_groupKey);
 
-    QStringList largeDefaults, smallDefaults;
-    qgcApp()->toolbox()->corePlugin()->valuesWidgetDefaultSettings(largeDefaults, smallDefaults);
+    _multiVehicleMgr = qgcApp()->toolbox()->multiVehicleManager();
+    connect(_multiVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &ValuesWidgetController::_activeVehicleChanged);
+
+    if (!forDefaultSettingsCreation) {
+        _loadSettings();
+    }
+}
+
+InstrumentValue* ValuesWidgetController::_createNewInstrumentValueWorker(Vehicle* activeVehicle, int fontSize, QmlObjectListModel* rowModel)
+{
+    InstrumentValue* newValue = new InstrumentValue(activeVehicle, fontSize, rowModel);
+
+    connect(newValue, &InstrumentValue::factChanged,            this, &ValuesWidgetController::_saveSettings);
+    connect(newValue, &InstrumentValue::factGroupNameChanged,   this, &ValuesWidgetController::_saveSettings);
+    connect(newValue, &InstrumentValue::labelChanged,           this, &ValuesWidgetController::_saveSettings);
+    connect(newValue, &InstrumentValue::fontSizeChanged,        this, &ValuesWidgetController::_saveSettings);
+    connect(newValue, &InstrumentValue::showUnitsChanged,       this, &ValuesWidgetController::_saveSettings);
+
+    return newValue;
+}
 
-    _largeValues = settings.value(_largeValuesKey, largeDefaults).toStringList();
-    _smallValues = settings.value(_smallValuesKey, smallDefaults).toStringList();
 
-    _altitudeProperties << "altitudeRelative" << "altitudeAMSL";
+InstrumentValue* ValuesWidgetController::appendColumn(int rowIndex)
+{
+    InstrumentValue* newValue = nullptr;
 
-    // Keep back compat for removed WGS84 value
-    if (_largeValues.contains ("Vehicle.altitudeWGS84")) {
-        setLargeValues(_largeValues.replaceInStrings("Vehicle.altitudeWGS84", "Vehicle.altitudeRelative"));
+    if (rowIndex >= 0 && rowIndex < _valuesModel->count()) {
+        QmlObjectListModel* row = _valuesModel->value<QmlObjectListModel*>(rowIndex);
+        int fontSize = InstrumentValue::DefaultFontSize;
+        if (row->count()) {
+            fontSize = row->value<InstrumentValue*>(0)->fontSize();
+        }
+        newValue  = _createNewInstrumentValueWorker(_currentActiveVehicle(), fontSize, row);
+        row->append(newValue);
     }
-    if (_smallValues.contains ("Vehicle.altitudeWGS84")) {
-        setSmallValues(_largeValues.replaceInStrings("Vehicle.altitudeWGS84", "Vehicle.altitudeRelative"));
+    _saveSettings();
+
+    return newValue;
+}
+
+void ValuesWidgetController::deleteLastColumn(int rowIndex)
+{
+    if (rowIndex >= 0 && rowIndex < _valuesModel->count()) {
+        QmlObjectListModel* row = _valuesModel->value<QmlObjectListModel*>(rowIndex);
+
+        if (rowIndex != 0 || row->count() > 1) {
+            row->removeAt(row->count() - 1);
+        }
+        if (row->count() == 0) {
+            // Last column was deleted, delete the row as well
+            _valuesModel->removeAt(rowIndex);
+        }
     }
+    _saveSettings();
 }
 
-void ValuesWidgetController::setLargeValues(const QStringList& values)
+QmlObjectListModel* ValuesWidgetController::appendRow(bool addBlanksColumn)
 {
+    QmlObjectListModel* newRow = new QmlObjectListModel(_valuesModel);
+    _valuesModel->append(newRow);
+    int rowIndex = _valuesModel->count() - 1;
+    if (addBlanksColumn) {
+        appendColumn(rowIndex);
+    }
+    _saveSettings();
+    return newRow;
+}
+
+QmlObjectListModel* ValuesWidgetController::insertRow(int atIndex, bool addBlanksColumn)
+{
+    QmlObjectListModel* newRow = nullptr;
+
+    if (atIndex >= 0 && atIndex < _valuesModel->count() + 1) {
+        QmlObjectListModel* newRow = new QmlObjectListModel(_valuesModel);
+        _valuesModel->insert(atIndex, newRow);
+        if (addBlanksColumn) {
+            appendColumn(atIndex);
+        }
+        _saveSettings();
+    }
+    return newRow;
+}
+
+void ValuesWidgetController::deleteRow(int rowIndex)
+{
+    if (rowIndex >= 0 && rowIndex < _valuesModel->count()) {
+        if (_valuesModel->count() > 1) {
+            _valuesModel->removeAt(rowIndex);
+        }
+        _saveSettings();
+    }
+}
+
+Vehicle* ValuesWidgetController::_currentActiveVehicle(void)
+{
+    Vehicle* activeVehicle = _multiVehicleMgr->activeVehicle();
+    if (!activeVehicle) {
+        activeVehicle = _multiVehicleMgr->offlineEditingVehicle();
+    }
+    return activeVehicle;
+}
+
+void ValuesWidgetController::_activeVehicleChanged(Vehicle* activeVehicle)
+{
+    if (!activeVehicle) {
+        activeVehicle = _currentActiveVehicle();
+    }
+
+    for (int rowIndex=0; rowIndex<_valuesModel->count(); rowIndex++) {
+        QmlObjectListModel* rowModel = _valuesModel->value<QmlObjectListModel*>(rowIndex);
+        for (int colIndex=0; colIndex<rowModel->count(); colIndex++) {
+            rowModel->value<InstrumentValue*>(colIndex)->activeVehicleChanged(activeVehicle);
+        }
+    }
+}
+
+bool ValuesWidgetController::_validRowIndex(int rowIndex)
+{
+    return rowIndex >= 0 && rowIndex < _valuesModel->count();
+}
+
+
+int ValuesWidgetController::fontSizeForRow(int rowIndex)
+{
+    return _validRowIndex(rowIndex) ? _rgFontSizeByRow[rowIndex].toInt() : _rgFontSizeByRow[0].toInt();
+}
+
+void ValuesWidgetController::setFontSizeForRow(int rowIndex, int fontSize)
+{
+    if (_validRowIndex(rowIndex)) {
+        _rgFontSizeByRow[rowIndex] = fontSize;
+    }
+}
+
+void ValuesWidgetController::_saveSettings(void)
+{
+    if (_preventSaveSettings) {
+        return;
+    }
+
     QSettings settings;
 
     settings.beginGroup(_groupKey);
-    settings.setValue(_largeValuesKey, values);
+    settings.remove(QStringLiteral(""));
+    settings.beginWriteArray(_rowsKey);
+
+    for (int rowIndex=0; rowIndex<_valuesModel->count(); rowIndex++) {
+        QmlObjectListModel* colValuesModel = _valuesModel->value<QmlObjectListModel*>(rowIndex);
+
+        settings.setArrayIndex(rowIndex);
+        settings.beginWriteArray(_columnsKey);
+
+        for (int colIndex=0; colIndex<colValuesModel->count(); colIndex++) {
+            settings.setArrayIndex(colIndex);
+            colValuesModel->value<InstrumentValue*>(colIndex)->saveToSettings(settings);
+        }
+
+        settings.endArray();
+    }
+
+    settings.endArray();
+}
+
+
+void ValuesWidgetController::_loadSettings(void)
+{
+    QSettings settings;
+
+    _valuesModel->deleteLater();
+    _valuesModel = new QmlObjectListModel(this);
+    emit valuesModelChanged(_valuesModel);
+
+    if (settings.childGroups().contains(_deprecatedGroupKey)) {
+        settings.beginGroup(_deprecatedGroupKey);
+
+        QStringList largeValues = settings.value(_deprecatedLargeValuesKey).toStringList();
+        QStringList smallValues = settings.value(_deprecatedSmallValuesKey).toStringList();
+        QStringList altitudeProperties = { "altitudeRelative" , "altitudeAMSL" };
+
+        int rowIndex = -1;
+        int valueCount = 0;
+        QmlObjectListModel* rowModel = nullptr;
+        for (const QString& largeValue: largeValues) {
+            QStringList parts = largeValue.split(".");
+
+            rowModel = appendRow(false /* addBlankColumn */);
+            rowIndex++;
+
+            InstrumentValue* colValue = appendColumn(rowIndex);
+            colValue->setFact(parts[0], parts[1], QString());
+            colValue->setLabel(colValue->fact()->shortDescription());
+            colValue->setShowUnits(true);
+            colValue->setFontSize(altitudeProperties.contains(parts[1]) ? InstrumentValue::LargeFontSize : InstrumentValue::DefaultFontSize);
+        }
 
-    _largeValues = values;
-    emit largeValuesChanged(values);
+        valueCount = 0;
+        rowModel = nullptr;
+        for (const QString& smallValue: smallValues) {
+            QStringList parts = smallValue.split(".");
+
+            if (!(valueCount++ & 1)) {
+                rowModel = appendRow(false /* addBlankColumn */);
+                rowIndex++;
+            }
+
+            InstrumentValue* colValue = appendColumn(rowIndex);
+            colValue->setFact(parts[0], parts[1], QString());
+            colValue->setLabel(colValue->fact()->shortDescription());
+            colValue->setShowUnits(true);
+            colValue->setFontSize(InstrumentValue::SmallFontSize);
+        }
+
+        settings.endGroup();
+        settings.remove(_deprecatedGroupKey);
+    } else {
+        _preventSaveSettings = true;
+
+        settings.beginGroup(_groupKey);
+        int cRows = settings.beginReadArray(_rowsKey);
+
+        for (int rowIndex=0; rowIndex<cRows; rowIndex++) {
+            appendRow(false /* addBlankColumns */);
+
+            settings.setArrayIndex(rowIndex);
+            int cCols = settings.beginReadArray(_columnsKey);
+
+            for (int colIndex=0; colIndex<cCols; colIndex++) {
+                settings.setArrayIndex(colIndex);
+                appendColumn(rowIndex)->readFromSettings(settings);
+            }
+
+            settings.endArray();
+        }
+
+        settings.endArray();
+
+        _preventSaveSettings = false;
+    }
+
+    // Use defaults if nothing there
+    if (_valuesModel->count() == 0) {
+        _valuesModel->deleteLater();
+        _valuesModel = qgcApp()->toolbox()->corePlugin()->valuesWidgetDefaultSettings(this);
+        emit valuesModelChanged(_valuesModel);
+    }
 }
 
-void ValuesWidgetController::setSmallValues(const QStringList& values)
+void ValuesWidgetController::resetToDefaults(void)
 {
     QSettings settings;
 
     settings.beginGroup(_groupKey);
-    settings.setValue(_smallValuesKey, values);
+    settings.remove("");
+
+    _loadSettings();
+}
+
+void ValuesWidgetController::setPreventSaveSettings(bool preventSaveSettings)
+{
+    _preventSaveSettings = preventSaveSettings;
+}
+
+InstrumentValue::InstrumentValue(Vehicle* activeVehicle, int fontSize, QmlObjectListModel* rowModel)
+    : QObject       (rowModel)
+    , _activeVehicle(activeVehicle)
+    , _rowModel     (rowModel)
+    , _fontSize     (fontSize)
+{
+
+}
+
+void InstrumentValue::activeVehicleChanged(Vehicle* activeVehicle)
+{
+    _activeVehicle = activeVehicle;
+
+    if (_fact) {
+        _fact = nullptr;
+
+        FactGroup* factGroup = nullptr;
+        QString factName;
+        if (_factGroupName == QStringLiteral("Vehicle")) {
+            factGroup = _activeVehicle;
+        } else {
+            factGroup = _activeVehicle->getFactGroup(_factGroupName);
+        }
+
+        if (factGroup) {
+            _fact = factGroup->getFact(factName);
+        }
+        emit factChanged(_fact);
+    }
+}
+
+void InstrumentValue::setFact(QString factGroupName, QString factName, QString label)
+{
+    if (_fact) {
+        _fact = nullptr;
+    }
+
+    FactGroup* factGroup = nullptr;
+    if (factGroupName == QStringLiteral("Vehicle")) {
+        factGroup = _activeVehicle;
+    } else {
+        factGroup = _activeVehicle->getFactGroup(factGroupName);
+    }
+
+    if (factGroup) {
+        _fact = factGroup->getFact(factName);
+    }
+
+    if (_fact) {
+        _factGroupName = factGroupName;
+        _label = label;
+    } else {
+        _factGroupName.clear();
+        _label.clear();
+    }
+
+    emit labelChanged(_label);
+    emit factChanged(_fact);
+    emit factGroupNameChanged(_factGroupName);
+}
+
+void InstrumentValue::_setFontSize(int fontSize)
+{
+    if (fontSize != _fontSize) {
+        _fontSize = fontSize;
+        emit fontSizeChanged(fontSize);
+    }
+}
+
+void InstrumentValue::setFontSize(int fontSize)
+{
+    _setFontSize(fontSize);
+
+    // All other items in row must change to match
+    for (int i=0; i<_rowModel->count(); i++) {
+        InstrumentValue* instrumentValue = _rowModel->value<InstrumentValue*>(i);
+        if (instrumentValue != this) {
+            instrumentValue->_setFontSize(fontSize);
+        }
+    }
+}
+
+void InstrumentValue::saveToSettings(QSettings& settings) const
+{
+    if (_fact) {
+        settings.setValue(_factGroupNameKey,    _factGroupName);
+        settings.setValue(_factNameKey,         _fact->name());
+    } else {
+        settings.setValue(_factGroupNameKey,    "");
+        settings.setValue(_factNameKey,         "");
+    }
+    settings.setValue(_labelKey,        _label);
+    settings.setValue(_fontSizeKey,     _fontSize);
+    settings.setValue(_showUnitsKey,    _showUnits);
+}
+
+void InstrumentValue::readFromSettings(const QSettings& settings)
+{
+    _factGroupName =    settings.value(_factGroupNameKey).toString();
+    _label =            settings.value(_labelKey).toString();
+    _fontSize =         settings.value(_fontSizeKey).toInt();
+    _showUnits =        settings.value(_showUnitsKey).toBool();
+
+    QString factName = settings.value(_factNameKey).toString();
+    if (!factName.isEmpty()) {
+        setFact(_factGroupName, factName, _label);
+    }
+
+    emit factChanged            (_fact);
+    emit factGroupNameChanged   (_factGroupName);
+    emit labelChanged           (_label);
+    emit fontSizeChanged        (_fontSize);
+    emit showUnitsChanged       (_showUnits);
+}
+
+void InstrumentValue::setLabel(const QString& label)
+{
+    if (label != _label) {
+        _label = label;
+        emit labelChanged(label);
+    }
+}
+
+void InstrumentValue::setShowUnits(bool showUnits)
+{
+    if (showUnits != _showUnits) {
+        _showUnits = showUnits;
+        emit showUnitsChanged(showUnits);
+    }
+}
+
+void InstrumentValue::clearFact(void)
+{
+    _fact = nullptr;
+    _factGroupName.clear();
+    _label.clear();
+    _showUnits = true;
 
-    _smallValues = values;
-    emit smallValuesChanged(values);
+    emit factChanged            (_fact);
+    emit factGroupNameChanged   (_factGroupName);
+    emit labelChanged           (_label);
+    emit showUnitsChanged       (_showUnits);
 }
diff --git a/src/FlightMap/Widgets/ValuesWidgetController.h b/src/FlightMap/Widgets/ValuesWidgetController.h
index d66b9bd7cb..92398ae56b 100644
--- a/src/FlightMap/Widgets/ValuesWidgetController.h
+++ b/src/FlightMap/Widgets/ValuesWidgetController.h
@@ -7,42 +7,124 @@
  *
  ****************************************************************************/
 
+#pragma once
 
-#ifndef ValuesWidgetController_H
-#define ValuesWidgetController_H
+#include "FactSystem.h"
+#include "QmlObjectListModel.h"
+#include "QGCApplication.h"
 
 #include <QObject>
 
+class ValuesWidgetController;
+
+class InstrumentValue : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum FontSize {
+        DefaultFontSize=0,
+        SmallFontSize,
+        MediumFontSize,
+        LargeFontSize
+    };
+
+    InstrumentValue(Vehicle* activeVehicle, int fontSize, QmlObjectListModel* rowModel);
+
+    Q_PROPERTY(QString  factGroupName   MEMBER  _factGroupName                      NOTIFY factGroupNameChanged)
+    Q_PROPERTY(Fact*    fact            READ    fact                                NOTIFY factChanged)
+    Q_PROPERTY(QString  label           READ    label           WRITE setLabel      NOTIFY labelChanged)
+    Q_PROPERTY(int      fontSize        READ    fontSize        WRITE setFontSize   NOTIFY fontSizeChanged)
+    Q_PROPERTY(bool     showUnits       READ    showUnits       WRITE setShowUnits  NOTIFY showUnitsChanged)
+
+    Q_INVOKABLE void setFact(QString factGroupName, QString factName, QString label);
+    Q_INVOKABLE void clearFact(void);
+
+    Fact*   fact                    (void) { return _fact; }
+    int     fontSize                (void) const { return _fontSize; }
+    QString label                   (void) const { return _label; }
+    bool    showUnits               (void) const { return _showUnits; }
+    void    setFontSize             (int fontSize);
+    void    setLabel                (const QString& label);
+    void    setShowUnits            (bool showUnits);
+    void    activeVehicleChanged    (Vehicle* activeVehicle);
+    void    saveToSettings          (QSettings& settings) const;
+    void    readFromSettings        (const QSettings& settings);
+
+signals:
+    void factChanged            (Fact* fact);
+    void factGroupNameChanged   (QString factGroup);
+    void labelChanged           (QString label);
+    void fontSizeChanged        (int fontSize);
+    void showUnitsChanged       (bool showUnits);
+
+private slots:
+
+private:
+    void _setFontSize(int fontSize);
+
+    Vehicle*            _activeVehicle =    nullptr;
+    QmlObjectListModel* _rowModel =         nullptr;
+    Fact*               _fact =             nullptr;
+    QString             _factGroupName;
+    QString             _label;
+    bool                _showUnits =        true;
+    int                 _fontSize =         DefaultFontSize;
+
+    static const char*  _factGroupNameKey;
+    static const char*  _factNameKey;
+    static const char*  _labelKey;
+    static const char*  _fontSizeKey;
+    static const char*  _showUnitsKey;
+};
+
 class ValuesWidgetController : public QObject
 {
     Q_OBJECT
     
 public:
-    ValuesWidgetController(void);
+    ValuesWidgetController(bool forDefaultSettingsCreation = false);
 
-    Q_PROPERTY(QStringList largeValues READ largeValues WRITE setLargeValues NOTIFY largeValuesChanged)
-    Q_PROPERTY(QStringList smallValues READ smallValues WRITE setSmallValues NOTIFY smallValuesChanged)
+    Q_PROPERTY(QmlObjectListModel* valuesModel READ valuesModel NOTIFY valuesModelChanged)
 
-    Q_PROPERTY(QStringList altitudeProperties READ altitudeProperties CONSTANT)
+    Q_INVOKABLE InstrumentValue*    appendColumn        (int rowIndex);
+    Q_INVOKABLE void                deleteLastColumn    (int rowIndex);
+    Q_INVOKABLE QmlObjectListModel* appendRow           (bool addBlanksColumn = true);
+    Q_INVOKABLE QmlObjectListModel* insertRow           (int atIndex, bool addBlanksColumn = true);
+    Q_INVOKABLE void                deleteRow           (int rowIndex);
+    Q_INVOKABLE int                 fontSizeForRow      (int rowIndex);
+    Q_INVOKABLE void                setFontSizeForRow   (int rowIndex, int fontSize);
+    Q_INVOKABLE void                resetToDefaults     (void);
 
-    QStringList largeValues(void) const { return _largeValues; }
-    QStringList smallValues(void) const { return _smallValues; }
-    void setLargeValues(const QStringList& values);
-    void setSmallValues(const QStringList& values);
-    QStringList altitudeProperties(void) const { return _altitudeProperties; }
+    QmlObjectListModel* valuesModel(void) { return _valuesModel; }
+
+    /// Turn on/off saving changes to QSettings
+    void setPreventSaveSettings(bool preventSaveSettings);
 
 signals:
-    void largeValuesChanged(QStringList values);
-    void smallValuesChanged(QStringList values);
+    void valuesModelChanged(QmlObjectListModel* valuesModel);
+
+private slots:
+    void        _activeVehicleChanged(Vehicle* activeVehicle);
+    Vehicle*    _currentActiveVehicle(void);
+    void        _saveSettings  (void);
 
 private:
-    QStringList _largeValues;
-    QStringList _smallValues;
-    QStringList _altitudeProperties;
+    bool                _validRowIndex                      (int rowIndex);
+    InstrumentValue*    _createNewInstrumentValueWorker     (Vehicle* activeVehicle, int fontSize, QmlObjectListModel* rowModel);
+    void                _loadSettings                       (void);
+
+    MultiVehicleManager*    _multiVehicleMgr =      nullptr;
+    QmlObjectListModel*     _valuesModel =          nullptr;
+    QVariantList            _rgFontSizeByRow;
+    bool                    _preventSaveSettings =  false;
 
     static const char* _groupKey;
-    static const char* _largeValuesKey;
-    static const char* _smallValuesKey;
-};
+    static const char* _rowsKey;
+    static const char* _columnsKey;
+
+    static const char* _deprecatedGroupKey;
+    static const char* _deprecatedLargeValuesKey;
+    static const char* _deprecatedSmallValuesKey;
 
-#endif
+};
diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc
index 4d0614164e..89d7522e30 100644
--- a/src/QGCApplication.cc
+++ b/src/QGCApplication.cc
@@ -100,6 +100,7 @@
 #include "LogReplayLink.h"
 #include "VehicleObjectAvoidance.h"
 #include "TrajectoryPoints.h"
+#include "ValuesWidgetController.h"
 
 #if defined(QGC_ENABLE_PAIRING)
 #include "PairingManager.h"
@@ -523,6 +524,7 @@ void QGCApplication::_initCommon()
     qmlRegisterUncreatableType<QGCMapPolygon>       ("QGroundControl.FlightMap",            1, 0, "QGCMapPolygon",              kRefOnly);
     qmlRegisterUncreatableType<QGCGeoBoundingCube>  ("QGroundControl.FlightMap",            1, 0, "QGCGeoBoundingCube",         kRefOnly);
     qmlRegisterUncreatableType<TrajectoryPoints>    ("QGroundControl.FlightMap",            1, 0, "TrajectoryPoints",           kRefOnly);
+    qmlRegisterUncreatableType<InstrumentValue>     (kQGCControllers,                       1, 0, "InstrumentValue",           kRefOnly);
 
     qmlRegisterType<QGCMapCircle>                   ("QGroundControl.FlightMap",            1, 0, "QGCMapCircle");
 
diff --git a/src/QmlControls/PageView.qml b/src/QmlControls/PageView.qml
index bece286815..3cff0b39c7 100644
--- a/src/QmlControls/PageView.qml
+++ b/src/QmlControls/PageView.qml
@@ -17,6 +17,7 @@ Rectangle {
     property real   _margins:           ScreenTools.defaultFontPixelWidth / 2
     property real   _pageWidth:         _root.width
     property var    _instrumentPages:   QGroundControl.corePlugin.instrumentPages
+    property bool   _settingsUnlocked:  false
 
     QGCPalette { id:qgcPal; colorGroupEnabled: parent.enabled }
 
@@ -29,11 +30,13 @@ Rectangle {
         centeredLabel:  true
         font.pointSize: ScreenTools.smallFontPointSize
 
+        onCurrentIndexChanged: _settingsUnlocked = false
+
         QGCColoredImage {
             anchors.leftMargin:     _margins
             anchors.left:           parent.left
             anchors.verticalCenter: parent.verticalCenter
-            source:                 "/res/gear-black.svg"
+            source:                 pageWidgetLoader.item.showLockIcon ? (_settingsUnlocked ? "/res/LockOpen.svg" : "/res/LockClosed.svg") : "/res/gear-black.svg"
             mipmap:                 true
             height:                 parent.height * 0.7
             width:                  height
@@ -44,7 +47,14 @@ Rectangle {
 
             QGCMouseArea {
                 fillItem:   parent
-                onClicked:  pageWidgetLoader.item.showSettings()
+                onClicked: {
+                    if (pageWidgetLoader.item.showLockIcon) {
+                        _settingsUnlocked = !_settingsUnlocked
+                        pageWidgetLoader.item.showSettings(_settingsUnlocked)
+                    } else {
+                        pageWidgetLoader.item.showSettings()
+                    }
+                }
             }
         }
     }
diff --git a/src/QmlControls/QGCLabel.qml b/src/QmlControls/QGCLabel.qml
index ef0bdede88..26afb1b8e8 100644
--- a/src/QmlControls/QGCLabel.qml
+++ b/src/QmlControls/QGCLabel.qml
@@ -1,9 +1,8 @@
-import QtQuick 2.3
-import QtQuick.Controls 1.2
-import QtQuick.Controls.Styles 1.4
+import QtQuick                  2.12
+import QtQuick.Controls         2.12
 
-import QGroundControl.Palette 1.0
-import QGroundControl.ScreenTools 1.0
+import QGroundControl.Palette       1.0
+import QGroundControl.ScreenTools   1.0
 
 Text {
     font.pointSize: ScreenTools.defaultFontPointSize
diff --git a/src/api/QGCCorePlugin.cc b/src/api/QGCCorePlugin.cc
index 48be2c132a..5c841cf3ba 100644
--- a/src/api/QGCCorePlugin.cc
+++ b/src/api/QGCCorePlugin.cc
@@ -22,6 +22,7 @@
 #endif
 #include "QGCLoggingCategory.h"
 #include "QGCCameraManager.h"
+#include "ValuesWidgetController.h"
 
 #include <QtQml>
 #include <QQmlEngine>
@@ -406,10 +407,40 @@ QString QGCCorePlugin::showAdvancedUIMessage() const
               "Are you sure you want to enable Advanced Mode?");
 }
 
-void QGCCorePlugin::valuesWidgetDefaultSettings(QStringList& largeValues, QStringList& smallValues)
+QmlObjectListModel* QGCCorePlugin::valuesWidgetDefaultSettings(QObject* valuesModelParent)
 {
-    Q_UNUSED(smallValues);
-    largeValues << "Vehicle.altitudeRelative" << "Vehicle.groundSpeed" << "Vehicle.flightTime";
+    ValuesWidgetController controller(true /* forDefaultSettingsCreation */);
+
+    // We don't want these to get written out to settings. This way if the user doesn't modify them
+    // they will get new changes to default settings from newer builds automatically on next run.
+    controller.setPreventSaveSettings(true);
+
+    QmlObjectListModel* columnModel = controller.appendRow();
+    InstrumentValue* colValue = columnModel->value<InstrumentValue*>(0);
+    colValue->setFact("Vehicle", "altitudeRelative", QString());
+    colValue->setLabel(colValue->fact()->shortDescription());
+    colValue->setShowUnits(true);
+    colValue->setFontSize(InstrumentValue::LargeFontSize);
+
+    columnModel = controller.appendRow();
+    colValue = columnModel->value<InstrumentValue*>(0);
+    colValue->setFact("Vehicle", "groundSpeed", QString());
+    colValue->setLabel(colValue->fact()->shortDescription());
+    colValue->setShowUnits(true);
+    colValue->setFontSize(InstrumentValue::DefaultFontSize);
+
+    columnModel = controller.appendRow();
+    colValue = columnModel->value<InstrumentValue*>(0);
+    colValue->setFact("Vehicle", "flightTime", QString());
+    colValue->setLabel(colValue->fact()->shortDescription());
+    colValue->setShowUnits(false);
+    colValue->setFontSize(InstrumentValue::DefaultFontSize);
+
+    controller.setPreventSaveSettings(false);
+
+    controller.valuesModel()->setParent(valuesModelParent);
+
+    return controller.valuesModel();
 }
 
 QQmlApplicationEngine* QGCCorePlugin::createRootWindow(QObject *parent)
diff --git a/src/api/QGCCorePlugin.h b/src/api/QGCCorePlugin.h
index 865bbf68b0..e8aa5eb70c 100644
--- a/src/api/QGCCorePlugin.h
+++ b/src/api/QGCCorePlugin.h
@@ -105,8 +105,9 @@ public:
     /// Allows a plugin to override the specified color name from the palette
     virtual void paletteOverride(QString colorName, QGCPalette::PaletteColorInfo_t& colorInfo);
 
-    /// Allows the plugin to override the default settings for the Values Widget large and small values
-    virtual void valuesWidgetDefaultSettings(QStringList& largeValues, QStringList& smallValues);
+    /// Return the default Intrument Value model for the Values Widget. The returned model will be
+    /// re-parented to valuesModelParent for ownership.
+    virtual QmlObjectListModel* valuesWidgetDefaultSettings(QObject* valuesModelParent);
 
     /// Allows the plugin to override the creation of the root (native) window.
     virtual QQmlApplicationEngine* createRootWindow(QObject* parent);
-- 
GitLab