diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc
index e9bdf62ea3aba837bfdb6d9c4c8b52377de238ed..e31522a7c557a0d0215af30828b4858abe4daa1a 100644
--- a/qgroundcontrol.qrc
+++ b/qgroundcontrol.qrc
@@ -42,9 +42,12 @@
src/AutoPilotPlugins/PX4/PowerComponent.qml
src/AutoPilotPlugins/PX4/PowerComponentSummary.qml
src/VehicleSetup/PX4FlowSensor.qml
+
+ src/QmlControls/QGroundControl.Controls.qmldir
src/QmlControls/ClickableColor.qml
src/QmlControls/DropButton.qml
src/QmlControls/ExclusiveGroupItem.qml
+ src/QmlControls/FactSliderPanel.qml
src/QmlControls/IndicatorButton.qml
src/QmlControls/JoystickThumbPad.qml
src/ui/toolbar/MainToolBar.qml
@@ -73,14 +76,16 @@
src/QmlControls/QGCViewDialog.qml
src/QmlControls/QGCViewMessage.qml
src/QmlControls/QGCViewPanel.qml
- src/QmlControls/QGroundControl.Controls.qmldir
src/QmlControls/RoundButton.qml
src/ui/toolbar/SignalStrength.qml
src/QmlControls/SubMenuButton.qml
src/QmlControls/VehicleRotationCal.qml
src/QmlControls/VehicleSummaryRow.qml
src/ViewWidgets/ViewWidget.qml
- src/QmlControls/FactSliderPanel.qml
+
+ src/MissionEditor/SimpleItemEditor.qml
+ src/MissionEditor/SurveyItemEditor.qml
+
src/FactSystem/FactControls/FactBitmask.qml
src/FactSystem/FactControls/FactCheckBox.qml
src/FactSystem/FactControls/FactComboBox.qml
diff --git a/src/JsonHelper.cc b/src/JsonHelper.cc
index d3738d656d81c86fdfc451bea559b50536a94a58..367a15b3fd411e45965ba89c660745efe526f225 100644
--- a/src/JsonHelper.cc
+++ b/src/JsonHelper.cc
@@ -82,7 +82,19 @@ bool JsonHelper::toQGeoCoordinate(const QJsonValue& jsonValue, QGeoCoordinate& c
return true;
}
-bool JsonHelper::validateKeyTypes(QJsonObject& jsonObject, const QStringList& keys, const QList& types, QString& errorString)
+void JsonHelper::writeQGeoCoordinate(QJsonValue& jsonValue, const QGeoCoordinate& coordinate, bool writeAltitude)
+{
+ QJsonArray coordinateArray;
+
+ coordinateArray << coordinate.latitude() << coordinate.longitude();
+ if (writeAltitude) {
+ coordinateArray << coordinate.altitude();
+ }
+
+ jsonValue = QJsonValue(coordinateArray);
+}
+
+bool JsonHelper::validateKeyTypes(const QJsonObject& jsonObject, const QStringList& keys, const QList& types, QString& errorString)
{
for (int i=0; i& types, QString& errorString);
+ static bool validateKeyTypes(const QJsonObject& jsonObject, const QStringList& keys, const QList& types, QString& errorString);
static bool toQGeoCoordinate(const QJsonValue& jsonValue, QGeoCoordinate& coordinate, bool altitudeRequired, QString& errorString);
static bool parseEnum(QJsonObject& jsonObject, QStringList& enumStrings, QStringList& enumValues, QString& errorString);
+ static void writeQGeoCoordinate(QJsonValue& jsonValue, const QGeoCoordinate& coordinate, bool writeAltitude);
+
static const char* _enumStringsJsonKey;
static const char* _enumValuesJsonKey;
};
diff --git a/src/MissionEditor/MissionEditor.qml b/src/MissionEditor/MissionEditor.qml
index c30de05675bc80c67b6141ec28c1690e492cf928..7b8255cc8e85c86af09288ca39969a29c3fe7229 100644
--- a/src/MissionEditor/MissionEditor.qml
+++ b/src/MissionEditor/MissionEditor.qml
@@ -552,6 +552,7 @@ QGCView {
var index = controller.insertComplexMissionItem(coordinate, controller.visualItems.count)
setCurrentItem(index)
checked = false
+ addMissionItemsButton.checked = false
}
}
diff --git a/src/MissionEditor/MissionItemStatus.qml b/src/MissionEditor/MissionItemStatus.qml
index 7c9fe0154fb28c77bb43cbc11dfe0c337427372b..ced1b401e15cb2470c60be6b0538f880cda930b6 100644
--- a/src/MissionEditor/MissionItemStatus.qml
+++ b/src/MissionEditor/MissionItemStatus.qml
@@ -49,7 +49,6 @@ Rectangle {
property real _gradient: _statusValid ? Math.atan(_currentMissionItem.altDifference / _currentMissionItem.distance) : 0
property real _gradientPercent: isNaN(_gradient) ? 0 : _gradient * 100
property real _azimuth: _statusValid ? _currentMissionItem.azimuth : -1
- property real _isHomePosition: _statusValid ? _currentMissionItem.homePosition : false
property bool _statusValid: currentMissionItem != undefined
property string _distanceText: _statusValid ? _distance.toFixed(2) + " m" : ""
property string _altText: _statusValid ? _altDifference.toFixed(2) + " m" : ""
diff --git a/src/MissionEditor/SimpleItemEditor.qml b/src/MissionEditor/SimpleItemEditor.qml
new file mode 100644
index 0000000000000000000000000000000000000000..35a2049ae62240527aacb4fea9bfcfbf1fde7404
--- /dev/null
+++ b/src/MissionEditor/SimpleItemEditor.qml
@@ -0,0 +1,123 @@
+import QtQuick 2.2
+import QtQuick.Controls 1.2
+import QtQuick.Controls.Styles 1.2
+import QtQuick.Dialogs 1.2
+
+import QGroundControl.ScreenTools 1.0
+import QGroundControl.Vehicle 1.0
+import QGroundControl.Controls 1.0
+import QGroundControl.FactControls 1.0
+import QGroundControl.Palette 1.0
+
+// Editor for Simple mission items
+Rectangle {
+ id: valuesRect
+ width: availableWidth
+ height: valuesItem.height
+ color: qgcPal.windowShadeDark
+ visible: missionItem.isCurrentItem
+ radius: _radius
+
+ // The following properties must be available up the hierachy chain
+ //property real availableWidth ///< Width for control
+ //property var missionItem ///< Mission Item for editor
+
+ Item {
+ id: valuesItem
+ anchors.margins: _margin
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ height: valuesColumn.height + (_margin * 2)
+
+ Column {
+ id: valuesColumn
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ spacing: _margin
+
+ QGCLabel {
+ width: parent.width
+ wrapMode: Text.WordWrap
+ text: missionItem.sequenceNumber == 0 ?
+ "Planned home position." :
+ (missionItem.rawEdit ?
+ "Provides advanced access to all commands/parameters. Be very careful!" :
+ missionItem.commandDescription)
+ }
+
+ Repeater {
+ model: missionItem.comboboxFacts
+
+ Item {
+ width: valuesColumn.width
+ height: comboBoxFact.height
+
+ QGCLabel {
+ id: comboBoxLabel
+ anchors.baseline: comboBoxFact.baseline
+ text: object.name
+ visible: object.name != ""
+ }
+
+ FactComboBox {
+ id: comboBoxFact
+ anchors.right: parent.right
+ width: comboBoxLabel.visible ? _editFieldWidth : parent.width
+ indexModel: false
+ model: object.enumStrings
+ fact: object
+ }
+ }
+ }
+
+ Repeater {
+ model: missionItem.textFieldFacts
+
+ Item {
+ width: valuesColumn.width
+ height: textField.height
+
+ QGCLabel {
+ id: textFieldLabel
+ anchors.baseline: textField.baseline
+ text: object.name
+ }
+
+ FactTextField {
+ id: textField
+ anchors.right: parent.right
+ width: _editFieldWidth
+ showUnits: true
+ fact: object
+ visible: !_root.readOnly
+ }
+
+ FactLabel {
+ anchors.baseline: textFieldLabel.baseline
+ anchors.right: parent.right
+ fact: object
+ visible: _root.readOnly
+ }
+ }
+ }
+
+ Repeater {
+ model: missionItem.checkboxFacts
+
+ FactCheckBox {
+ text: object.name
+ fact: object
+ }
+ }
+
+ QGCButton {
+ text: "Move Home to map center"
+ visible: missionItem.homePosition
+ onClicked: editorRoot.moveHomeToMapCenter()
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ } // Column
+ } // Item
+} // Rectangle
diff --git a/src/MissionEditor/SurveyItemEditor.qml b/src/MissionEditor/SurveyItemEditor.qml
new file mode 100644
index 0000000000000000000000000000000000000000..14d54468034ab00b32543a599d0d81a970c9e873
--- /dev/null
+++ b/src/MissionEditor/SurveyItemEditor.qml
@@ -0,0 +1,62 @@
+import QtQuick 2.2
+import QtQuick.Controls 1.2
+
+import QGroundControl.ScreenTools 1.0
+import QGroundControl.Vehicle 1.0
+import QGroundControl.Controls 1.0
+import QGroundControl.FactControls 1.0
+import QGroundControl.Palette 1.0
+
+// Editor for Survery mission items
+Rectangle {
+ id: _root
+ height: editorColumn.height + (_margin * 2)
+ width: availableWidth
+ color: qgcPal.windowShadeDark
+ radius: _radius
+
+ // The following properties must be available up the hierachy chain
+ //property real availableWidth ///< Width for control
+ //property var missionItem ///< Mission Item for editor
+
+ property bool _addPointsMode: false
+ property real _margin: ScreenTools.defaultFontPixelWidth / 2
+
+ QGCPalette { id: qgcPal; colorGroupEnabled: true }
+
+ Column {
+ id: editorColumn
+ anchors.margins: _margin
+ anchors.top: parent.top
+ anchors.left: parent.left
+ width: availableWidth
+ spacing: _margin
+
+ Connections {
+ target: editorMap
+
+ onMapClicked: {
+ if (_addPointsMode) {
+ missionItem.addPolygonCoordinate(coordinate)
+ }
+ }
+ }
+
+ QGCLabel {
+ text: "Fly a grid pattern over a defined area."
+ wrapMode: Text.WordWrap
+ }
+
+ QGCButton {
+ text: _addPointsMode ? "Finished" : "Draw Polygon"
+ onClicked: {
+ if (_addPointsMode) {
+ _addPointsMode = false
+ } else {
+ missionItem.clearPolygon()
+ _addPointsMode = true
+ }
+ }
+ }
+ }
+}
diff --git a/src/MissionManager/ComplexMissionItem.cc b/src/MissionManager/ComplexMissionItem.cc
index e2381042215823624685bd4a81b24bf3846b5717..d148c9f1ddb0686c755839378f1f35424535f9a0 100644
--- a/src/MissionManager/ComplexMissionItem.cc
+++ b/src/MissionManager/ComplexMissionItem.cc
@@ -1,7 +1,7 @@
/*===================================================================
QGroundControl Open Source Ground Control Station
-(c) 2009, 2010 QGROUNDCONTROL PROJECT
+(c) 2009, 2016 QGROUNDCONTROL PROJECT
This file is part of the QGROUNDCONTROL project
@@ -21,12 +21,25 @@ This file is part of the QGROUNDCONTROL project
======================================================================*/
#include "ComplexMissionItem.h"
+#include "JsonHelper.h"
+#include "MissionController.h"
+
+const char* ComplexMissionItem::_jsonVersionKey = "version";
+const char* ComplexMissionItem::_jsonTypeKey = "type";
+const char* ComplexMissionItem::_jsonPolygonKey = "polygon";
+const char* ComplexMissionItem::_jsonIdKey = "id";
+
+const char* ComplexMissionItem::_complexType = "survey";
ComplexMissionItem::ComplexMissionItem(Vehicle* vehicle, QObject* parent)
: VisualMissionItem(vehicle, parent)
, _dirty(false)
{
+ MissionItem missionItem;
+ // FIXME: Bogus entries for testing
+ _missionItems += new MissionItem(this);
+ _missionItems += new MissionItem(this);
}
ComplexMissionItem::ComplexMissionItem(const ComplexMissionItem& other, QObject* parent)
@@ -36,20 +49,6 @@ ComplexMissionItem::ComplexMissionItem(const ComplexMissionItem& other, QObject*
}
-QVariantList ComplexMissionItem::polygonPath(void)
-{
- return _polygonPath;
-#if 0
- QVariantList list;
-
- list << QVariant::fromValue(QGeoCoordinate(-35.362686830000001, 149.16410282999999))
- << QVariant::fromValue(QGeoCoordinate(-35.362660579999996, 149.16606619999999))
- << QVariant::fromValue(QGeoCoordinate(-35.363832989999999, 149.16505769));
-
- return list;
-#endif
-}
-
void ComplexMissionItem::clearPolygon(void)
{
_polygonPath.clear();
@@ -60,6 +59,11 @@ void ComplexMissionItem::addPolygonCoordinate(const QGeoCoordinate coordinate)
{
_polygonPath << QVariant::fromValue(coordinate);
emit polygonPathChanged();
+
+ // FIXME: Hack, first polygon point sets entry coordinate
+ if (_polygonPath.count() == 1) {
+ setCoordinate(coordinate);
+ }
}
int ComplexMissionItem::nextSequenceNumber(void) const
@@ -72,6 +76,10 @@ void ComplexMissionItem::setCoordinate(const QGeoCoordinate& coordinate)
if (_coordinate != coordinate) {
_coordinate = coordinate;
emit coordinateChanged(_coordinate);
+ _missionItems[0]->setCoordinate(coordinate);
+
+ // FIXME: Hack
+ _setExitCoordinate(coordinate);
}
}
@@ -83,11 +91,148 @@ void ComplexMissionItem::setDirty(bool dirty)
}
}
-bool ComplexMissionItem::save(QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString)
+void ComplexMissionItem::save(QJsonObject& saveObject) const
+{
+ saveObject[_jsonVersionKey] = 1;
+ saveObject[_jsonTypeKey] = _complexType;
+ saveObject[_jsonIdKey] = sequenceNumber();
+
+ // Polygon shape
+
+ QJsonArray polygonArray;
+
+ for (int i=0; i<_polygonPath.count(); i++) {
+ const QVariant& polyVar = _polygonPath[i];
+
+ QJsonValue jsonValue;
+ JsonHelper::writeQGeoCoordinate(jsonValue, polyVar.value(), false /* writeAltitude */);
+ polygonArray += jsonValue;
+ }
+
+ saveObject[_jsonPolygonKey] = polygonArray;
+
+ // Base mission items
+
+ QJsonArray simpleItems;
+ for (int i=0; i<_missionItems.count(); i++) {
+ MissionItem* missionItem = _missionItems[i];
+
+ QJsonObject jsonObject;
+ missionItem->save(jsonObject);
+ simpleItems.append(jsonObject);
+ }
+ saveObject[MissionController::jsonSimpleItemsKey] = simpleItems;
+}
+
+void ComplexMissionItem::setSequenceNumber(int sequenceNumber)
+{
+ VisualMissionItem::setSequenceNumber(sequenceNumber);
+
+ // Update internal mission items to new numbering
+ for (int i=0; i<_missionItems.count(); i++) {
+ _missionItems[i]->setSequenceNumber(sequenceNumber++);
+ }
+}
+
+void ComplexMissionItem::_clear(void)
{
- Q_UNUSED(missionObject);
- Q_UNUSED(missionItems);
+ // Clear old settings
+ for (int i=0; i<_missionItems.count(); i++) {
+ _missionItems[i]->deleteLater();
+ }
+ _missionItems.clear();
+ _polygonPath.clear();
+}
+
+
+bool ComplexMissionItem::load(const QJsonObject& complexObject, QString& errorString)
+{
+ _clear();
+
+ // Validate requires keys
+ QStringList requiredKeys;
+ requiredKeys << _jsonVersionKey << _jsonTypeKey << _jsonIdKey << _jsonPolygonKey << MissionController::jsonSimpleItemsKey;
+ if (!JsonHelper::validateRequiredKeys(complexObject, requiredKeys, errorString)) {
+ _clear();
+ return false;
+ }
+
+ // Validate types
+ QStringList keyList;
+ QList typeList;
+ keyList << _jsonVersionKey << _jsonTypeKey << _jsonIdKey << _jsonPolygonKey << MissionController::jsonSimpleItemsKey;
+ typeList << QJsonValue::Double << QJsonValue::String << QJsonValue::Double << QJsonValue::Array << QJsonValue::Array;
+ if (!JsonHelper::validateKeyTypes(complexObject, keyList, typeList, errorString)) {
+ _clear();
+ return false;
+ }
+
+ // Version check
+ if (complexObject[_jsonVersionKey].toInt() != 1) {
+ errorString = tr("QGroundControl does not support this version of survey items");
+ _clear();
+ return false;
+ }
+ QString complexType = complexObject[_jsonTypeKey].toString();
+ if (complexType != _complexType) {
+ errorString = tr("QGroundControl does not support loading this complex mission item type: %1").arg(complexType);
+ _clear();
+ return false;
+ }
+
+ setSequenceNumber(complexObject[_jsonIdKey].toInt());
+
+ // Polygon shape
+ QJsonArray polygonArray(complexObject[_jsonPolygonKey].toArray());
+ for (int i=0; iload(itemValue.toObject(), errorString)) {
+ _missionItems.append(item);
+ } else {
+ _clear();
+ return false;
+ }
+ }
- errorString = "Complex save NYI";
- return false;
+ int itemCount = _missionItems.count();
+ if (itemCount > 0) {
+ setCoordinate(_missionItems[0]->coordinate());
+ _setExitCoordinate(_missionItems[itemCount - 1]->coordinate());
+ }
+
+ return true;
+}
+
+void ComplexMissionItem::_setExitCoordinate(const QGeoCoordinate& coordinate)
+{
+ if (_exitCoordinate != coordinate) {
+ _exitCoordinate = coordinate;
+ emit exitCoordinateChanged(coordinate);
+
+ int itemCount = _missionItems.count();
+ if (itemCount > 0) {
+ _missionItems[itemCount - 1]->setCoordinate(coordinate);
+ }
+ }
}
diff --git a/src/MissionManager/ComplexMissionItem.h b/src/MissionManager/ComplexMissionItem.h
index 956f6cc251fc7271f0c9ea22a403c8b6463c7eee..741800d187ad012ecb09680227741cf02e77235b 100644
--- a/src/MissionManager/ComplexMissionItem.h
+++ b/src/MissionManager/ComplexMissionItem.h
@@ -1,24 +1,24 @@
/*=====================================================================
-
+
QGroundControl Open Source Ground Control Station
-
- (c) 2009 - 2014 QGROUNDCONTROL PROJECT
-
+
+ (c) 2009 - 2016 QGROUNDCONTROL PROJECT
+
This file is part of the QGROUNDCONTROL project
-
+
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
-
+
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
-
+
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see .
-
+
======================================================================*/
#ifndef ComplexMissionItem_H
@@ -30,7 +30,7 @@
class ComplexMissionItem : public VisualMissionItem
{
Q_OBJECT
-
+
public:
ComplexMissionItem(Vehicle* vehicle, QObject* parent = NULL);
ComplexMissionItem(const ComplexMissionItem& other, QObject* parent = NULL);
@@ -40,13 +40,19 @@ public:
Q_INVOKABLE void clearPolygon(void);
Q_INVOKABLE void addPolygonCoordinate(const QGeoCoordinate coordinate);
- QVariantList polygonPath(void);
+ QVariantList polygonPath(void) { return _polygonPath; }
- const QList& missionItems(void) { return _missionItems; }
+ QList& missionItems(void) { return _missionItems; }
/// @return The next sequence number to use after this item. Takes into account child items of the complex item
int nextSequenceNumber(void) const;
+ /// Load the complex mission item from Json
+ /// @param complexObject Complex mission item json object
+ /// @param[out] errorString Error if load fails
+ /// @return true: load success, false: load failed, errorString set
+ bool load(const QJsonObject& complexObject, QString& errorString);
+
// Overrides from VisualMissionItem
bool dirty (void) const final { return _dirty; }
@@ -56,24 +62,36 @@ public:
QString commandDescription (void) const final { return "Survey"; }
QString commandName (void) const final { return "Survey"; }
QGeoCoordinate coordinate (void) const final { return _coordinate; }
- QGeoCoordinate exitCoordinate (void) const final { return _coordinate; }
+ QGeoCoordinate exitCoordinate (void) const final { return _exitCoordinate; }
bool coordinateHasRelativeAltitude (void) const final { return true; }
bool exitCoordinateHasRelativeAltitude (void) const final { return true; }
bool exitCoordinateSameAsEntry (void) const final { return false; }
void setDirty (bool dirty) final;
- void setCoordinate (const QGeoCoordinate& coordinate);
- bool save (QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString) final;
+ void setCoordinate (const QGeoCoordinate& coordinate) final;
+ void setSequenceNumber (int sequenceNumber) final;
+ void save (QJsonObject& saveObject) const final;
signals:
void polygonPathChanged(void);
private:
+ void _clear(void);
+ void _setExitCoordinate(const QGeoCoordinate& coordinate);
+
bool _dirty;
QVariantList _polygonPath;
QList _missionItems;
QGeoCoordinate _coordinate;
+ QGeoCoordinate _exitCoordinate;
+
+ static const char* _jsonVersionKey;
+ static const char* _jsonTypeKey;
+ static const char* _jsonPolygonKey;
+ static const char* _jsonIdKey;
+
+ static const char* _complexType;
};
#endif
diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc
index 717eaf2ca6de1d179c698d061936e67f09cb319d..debac5505610c2e4e09b1516d8470cbeb148f77b 100644
--- a/src/MissionManager/MissionController.cc
+++ b/src/MissionManager/MissionController.cc
@@ -29,6 +29,7 @@ This file is part of the QGROUNDCONTROL project
#include "QGCApplication.h"
#include "SimpleMissionItem.h"
#include "ComplexMissionItem.h"
+#include "JsonHelper.h"
#ifndef __mobile__
#include "QGCFileDialog.h"
@@ -36,11 +37,13 @@ This file is part of the QGROUNDCONTROL project
QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog")
+const char* MissionController::jsonSimpleItemsKey = "items";
+
const char* MissionController::_settingsGroup = "MissionController";
const char* MissionController::_jsonVersionKey = "version";
const char* MissionController::_jsonGroundStationKey = "groundStation";
const char* MissionController::_jsonMavAutopilotKey = "MAV_AUTOPILOT";
-const char* MissionController::_jsonItemsKey = "items";
+const char* MissionController::_jsonComplexItemsKey = "complexItems";
const char* MissionController::_jsonPlannedHomePositionKey = "plannedHomePosition";
MissionController::MissionController(QObject *parent)
@@ -140,7 +143,9 @@ void MissionController::sendMissionItems(void)
missionItem.setSequenceNumber(sequenceNumber++);
missionItems.append(&missionItem);
} else {
- foreach (MissionItem* missionItem, qobject_cast(visualItem)->missionItems()) {
+ ComplexMissionItem* complexItem = qobject_cast(visualItem);
+ for (int j=0; jmissionItems().count(); j++) {
+ MissionItem* missionItem = complexItem->missionItems()[i];
missionItem->setSequenceNumber(sequenceNumber++);
missionItems.append(missionItem);
}
@@ -247,29 +252,56 @@ bool MissionController::_loadJsonMissionFile(const QByteArray& bytes, QmlObjectL
QJsonObject json = jsonDoc.object();
- if (!json.contains(_jsonVersionKey)) {
- errorString = QStringLiteral("File is missing version key");
+ // Check for required keys
+ QStringList requiredKeys;
+ requiredKeys << _jsonVersionKey << _jsonPlannedHomePositionKey;
+ if (!JsonHelper::validateRequiredKeys(json, requiredKeys, errorString)) {
+ return false;
+ }
+
+ // Validate base key types
+ QStringList keyList;
+ QList typeList;
+ keyList << jsonSimpleItemsKey << _jsonVersionKey << _jsonGroundStationKey << _jsonMavAutopilotKey << _jsonComplexItemsKey << _jsonPlannedHomePositionKey;
+ typeList << QJsonValue::Array << QJsonValue::String << QJsonValue::String << QJsonValue::Double << QJsonValue::Array << QJsonValue::Object;
+ if (!JsonHelper::validateKeyTypes(json, keyList, typeList, errorString)) {
return false;
}
+
+ // Version check
if (json[_jsonVersionKey].toString() != "1.0") {
errorString = QStringLiteral("QGroundControl does not support this file version");
return false;
}
- if (json.contains(_jsonItemsKey)) {
- if (!json[_jsonItemsKey].isArray()) {
- errorString = QStringLiteral("items values must be array");
- return false;
+ // Simple items
+ if (json.contains(jsonSimpleItemsKey)) {
+ QJsonArray itemArray(json[jsonSimpleItemsKey].toArray());
+ foreach (const QJsonValue& itemValue, itemArray) {
+ if (!itemValue.isObject()) {
+ errorString = QStringLiteral("Mission item is not an object");
+ return false;
+ }
+
+ SimpleMissionItem* item = new SimpleMissionItem(_activeVehicle, this);
+ if (item->load(itemValue.toObject(), errorString)) {
+ missionItems->append(item);
+ } else {
+ return false;
+ }
}
+ }
- QJsonArray itemArray(json[_jsonItemsKey].toArray());
+ // Complex items
+ if (json.contains(_jsonComplexItemsKey)) {
+ QJsonArray itemArray(json[_jsonComplexItemsKey].toArray());
foreach (const QJsonValue& itemValue, itemArray) {
if (!itemValue.isObject()) {
errorString = QStringLiteral("Mission item is not an object");
return false;
}
- SimpleMissionItem* item = new SimpleMissionItem(_activeVehicle, this);
+ ComplexMissionItem* item = new ComplexMissionItem(_activeVehicle, this);
if (item->load(itemValue.toObject(), errorString)) {
missionItems->append(item);
} else {
@@ -418,12 +450,12 @@ void MissionController::_saveMissionToFile(const QString& filename)
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qgcApp()->showMessage(file.errorString());
} else {
- QJsonObject missionObject; // top level json object
- QJsonArray missionItemArray; // array of mission items
- QString errorString;
+ QJsonObject missionFileObject; // top level json object
+ QJsonArray simpleItemsObject;
+ QJsonArray complexItemsObject;
- missionObject[_jsonVersionKey] = "1.0";
- missionObject[_jsonGroundStationKey] = "QGroundControl";
+ missionFileObject[_jsonVersionKey] = "1.0";
+ missionFileObject[_jsonGroundStationKey] = "QGroundControl";
MAV_AUTOPILOT firmwareType = MAV_AUTOPILOT_GENERIC;
if (_activeVehicle) {
@@ -435,7 +467,7 @@ void MissionController::_saveMissionToFile(const QString& filename)
QSettings settings;
firmwareType = (MAV_AUTOPILOT)settings.value("OfflineEditingFirmwareType", MAV_AUTOPILOT_ARDUPILOTMEGA).toInt();
}
- missionObject[_jsonMavAutopilotKey] = firmwareType;
+ missionFileObject[_jsonMavAutopilotKey] = firmwareType;
// Save planned home position
QJsonObject homePositionObject;
@@ -446,19 +478,26 @@ void MissionController::_saveMissionToFile(const QString& filename)
qgcApp()->showMessage(QStringLiteral("Internal error: VisualMissionItem at index 0 not SimpleMissionItem"));
return;
}
- missionObject[_jsonPlannedHomePositionKey] = homePositionObject;
+ missionFileObject[_jsonPlannedHomePositionKey] = homePositionObject;
- // Save the remainder of the visual items
+ // Save the visual items
for (int i=1; i<_visualItems->count(); i++) {
+ QJsonObject itemObject;
+
VisualMissionItem* visualItem = qobject_cast(_visualItems->get(i));
- if (!visualItem->save(missionObject, missionItemArray, errorString)) {
- qgcApp()->showMessage(errorString);
- return;
+ visualItem->save(itemObject);
+
+ if (visualItem->isSimpleItem()) {
+ simpleItemsObject.append(itemObject);
+ } else {
+ complexItemsObject.append(itemObject);
}
}
- missionObject["items"] = missionItemArray;
- QJsonDocument saveDoc(missionObject);
+ missionFileObject[jsonSimpleItemsKey] = simpleItemsObject;
+ missionFileObject[_jsonComplexItemsKey] = complexItemsObject;
+
+ QJsonDocument saveDoc(missionFileObject);
file.write(saveDoc.toJson());
}
diff --git a/src/MissionManager/MissionController.h b/src/MissionManager/MissionController.h
index e9cef5cc34f572ca2868ceabf2532ca54101ebd6..5e5b33c7bf034779f29d95140a32bd908d846ebc 100644
--- a/src/MissionManager/MissionController.h
+++ b/src/MissionManager/MissionController.h
@@ -75,6 +75,8 @@ public:
void setAutoSync(bool autoSync);
bool syncInProgress(void);
+ static const char* jsonSimpleItemsKey; ///< Key for simple items in a json file
+
signals:
void visualItemsChanged(void);
void complexVisualItemsChanged(void);
@@ -131,7 +133,7 @@ private:
static const char* _jsonVersionKey;
static const char* _jsonGroundStationKey;
static const char* _jsonMavAutopilotKey;
- static const char* _jsonItemsKey;
+ static const char* _jsonComplexItemsKey;
static const char* _jsonPlannedHomePositionKey;
};
diff --git a/src/MissionManager/MissionItem.cc b/src/MissionManager/MissionItem.cc
index 1eaa10f0d2d22a2ff409ab5a4b9303133d814c88..ffb11d92f13cefb6d5e733dc5d750f10dbc86e4d 100644
--- a/src/MissionManager/MissionItem.cc
+++ b/src/MissionManager/MissionItem.cc
@@ -148,7 +148,7 @@ MissionItem::~MissionItem()
{
}
-void MissionItem::save(QJsonObject& json)
+void MissionItem::save(QJsonObject& json) const
{
json[_jsonTypeKey] = _itemType;
json[_jsonIdKey] = sequenceNumber();
diff --git a/src/MissionManager/MissionItem.h b/src/MissionManager/MissionItem.h
index d7bc93818bf0d6a2e38312e78bf91ad6cd63cc4e..876643039f5d7c59e09dc79c0cb7c89ea24964b2 100644
--- a/src/MissionManager/MissionItem.h
+++ b/src/MissionManager/MissionItem.h
@@ -103,7 +103,7 @@ public:
void setParam7 (double param7);
void setCoordinate (const QGeoCoordinate& coordinate);
- void save(QJsonObject& json);
+ void save(QJsonObject& json) const;
bool load(QTextStream &loadStream);
bool load(const QJsonObject& json, QString& errorString);
diff --git a/src/MissionManager/SimpleMissionItem.cc b/src/MissionManager/SimpleMissionItem.cc
index 55b69fc859b06285262b500715e2ec7e0061bca9..72d8a270b2244bd081726bbea423fdc0cd5792ee 100644
--- a/src/MissionManager/SimpleMissionItem.cc
+++ b/src/MissionManager/SimpleMissionItem.cc
@@ -244,16 +244,9 @@ SimpleMissionItem::~SimpleMissionItem()
{
}
-bool SimpleMissionItem::save(QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString)
+void SimpleMissionItem::save(QJsonObject& saveObject) const
{
- Q_UNUSED(missionObject);
- Q_UNUSED(errorString);
-
- QJsonObject itemObject;
- _missionItem.save(itemObject);
- missionItems.append(itemObject);
-
- return true;
+ _missionItem.save(saveObject);
}
bool SimpleMissionItem::load(QTextStream &loadStream)
@@ -585,3 +578,9 @@ void SimpleMissionItem::setCoordinate(const QGeoCoordinate& coordinate)
_missionItem.setCoordinate(coordinate);
}
}
+
+void SimpleMissionItem::setSequenceNumber(int sequenceNumber)
+{
+ _missionItem.setSequenceNumber(sequenceNumber);
+ VisualMissionItem::setSequenceNumber(sequenceNumber);
+}
diff --git a/src/MissionManager/SimpleMissionItem.h b/src/MissionManager/SimpleMissionItem.h
index 5f5b230e4ba89d9a61ea114a121d59840af71a8b..984e1c13247228b5ddb23c42dc8360713bbfc602 100644
--- a/src/MissionManager/SimpleMissionItem.h
+++ b/src/MissionManager/SimpleMissionItem.h
@@ -107,8 +107,9 @@ public:
bool exitCoordinateSameAsEntry (void) const final { return true; }
void setDirty (bool dirty) final;
- void setCoordinate (const QGeoCoordinate& coordinate);
- bool save (QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString) final;
+ void setCoordinate (const QGeoCoordinate& coordinate) final;
+ void setSequenceNumber (int sequenceNumber) final;
+ void save (QJsonObject& saveObject) const final;
public slots:
void setDefaultsForCommand(void);
diff --git a/src/MissionManager/VisualMissionItem.h b/src/MissionManager/VisualMissionItem.h
index fb91aea10b9283fe5d265773085ae27bb438ba65..5e553ee69de7c23ecff21a0ac89d7474277d7f19 100644
--- a/src/MissionManager/VisualMissionItem.h
+++ b/src/MissionManager/VisualMissionItem.h
@@ -108,10 +108,13 @@ public:
void setAltPercent (double altPercent);
void setAzimuth (double azimuth);
void setDistance (double distance);
- void setSequenceNumber (int sequenceNumber);
Vehicle* vehicle(void) { return _vehicle; }
+ // Virtuals which may be overriden by derived classes
+
+ virtual void setSequenceNumber(int sequenceNumber);
+
// Pure virtuals which must be provides by derived classes
virtual bool dirty (void) const = 0;
@@ -130,11 +133,9 @@ public:
virtual void setDirty (bool dirty) = 0;
virtual void setCoordinate (const QGeoCoordinate& coordinate) = 0;
- /// Save the item in Json format
- /// @param missionObject Top level object for entire mission file
- /// @param missionItems Array of mission items
- /// @return false: save failed, errorString set
- virtual bool save(QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString) = 0;
+ /// Save the item(s) in Json format
+ /// @param saveObject Save the item to this json object
+ virtual void save(QJsonObject& saveObject) const = 0;
signals:
void altDifferenceChanged (double altDifference);
diff --git a/src/QmlControls/MissionItemEditor.qml b/src/QmlControls/MissionItemEditor.qml
index fa5abe0e598c02bffb8833bfc7551862a8f9d343..72f3d233fbf8a4900e6a123a01ca618ad03c2c6c 100644
--- a/src/QmlControls/MissionItemEditor.qml
+++ b/src/QmlControls/MissionItemEditor.qml
@@ -15,7 +15,7 @@ Rectangle {
id: _root
height: editorLoader.y + editorLoader.height + (_margin * 2)
- color: missionItem.isCurrentItem ? qgcPal.buttonHighlight : qgcPal.windowShade
+ color: _currentItem ? qgcPal.buttonHighlight : qgcPal.windowShade
radius: _radius
property var missionItem ///< MissionItem associated with this editor
@@ -27,11 +27,12 @@ Rectangle {
signal insert(int i)
signal moveHomeToMapCenter
+ property bool _currentItem: missionItem.isCurrentItem
+ property color _outerTextColor: _currentItem ? "black" : qgcPal.text
+
readonly property real _editFieldWidth: ScreenTools.defaultFontPixelWidth * 16
readonly property real _margin: ScreenTools.defaultFontPixelWidth / 2
readonly property real _radius: ScreenTools.defaultFontPixelWidth / 2
- property color _outerTextColor: missionItem.isCurrentItem ? "black" : qgcPal.text
-
QGCPalette {
id: qgcPal
@@ -88,7 +89,7 @@ Rectangle {
MenuItem {
text: "Show all values"
checkable: true
- checked: missionItem.rawEdit
+ checked: missionItem.isSimpleItem ? missionItem.rawEdit : false
visible: missionItem.isSimpleItem
onTriggered: {
@@ -142,191 +143,10 @@ Rectangle {
anchors.topMargin: _margin
anchors.left: parent.left
anchors.top: commandPicker.bottom
- sourceComponent: missionItem.isSimpleItem ? simpleMissionItemEditor : complexMissionItemEditor
-
- /// How wide the loaded component should be
- property real availableWidth: _root.width - (_margin * 2)
- }
-
- Component {
- id: valuesComponent
-
- Rectangle {
- id: valuesRect
- height: valuesItem.height
- color: qgcPal.windowShadeDark
- visible: missionItem.isCurrentItem
- radius: _radius
-
- Item {
- id: valuesItem
- anchors.margins: _margin
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- height: valuesColumn.height + (_margin * 2)
-
- Column {
- id: valuesColumn
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- spacing: _margin
-
- QGCLabel {
- width: parent.width
- wrapMode: Text.WordWrap
- text: missionItem.sequenceNumber == 0 ?
- "Planned home position." :
- (missionItem.rawEdit ?
- "Provides advanced access to all commands/parameters. Be very careful!" :
- missionItem.commandDescription)
- }
-
- Repeater {
- model: missionItem.comboboxFacts
-
- Item {
- width: valuesColumn.width
- height: comboBoxFact.height
-
- QGCLabel {
- id: comboBoxLabel
- anchors.baseline: comboBoxFact.baseline
- text: object.name
- visible: object.name != ""
- }
-
- FactComboBox {
- id: comboBoxFact
- anchors.right: parent.right
- width: comboBoxLabel.visible ? _editFieldWidth : parent.width
- indexModel: false
- model: object.enumStrings
- fact: object
- }
- }
- }
-
- Repeater {
- model: missionItem.textFieldFacts
-
- Item {
- width: valuesColumn.width
- height: textField.height
-
- QGCLabel {
- id: textFieldLabel
- anchors.baseline: textField.baseline
- text: object.name
- }
-
- FactTextField {
- id: textField
- anchors.right: parent.right
- width: _editFieldWidth
- showUnits: true
- fact: object
- visible: !_root.readOnly
- }
-
- FactLabel {
- anchors.baseline: textFieldLabel.baseline
- anchors.right: parent.right
- fact: object
- visible: _root.readOnly
- }
- }
- }
-
- Repeater {
- model: missionItem.checkboxFacts
-
- FactCheckBox {
- text: object.name
- fact: object
- }
- }
-
- QGCButton {
- text: "Move Home to map center"
- visible: missionItem.homePosition
- onClicked: moveHomeToMapCenter()
- anchors.horizontalCenter: parent.horizontalCenter
- }
- } // Column
- } // Item
- } // Rectangle
+ height: _currentItem && item ? item.height : 0
+ source: _currentItem ? (missionItem.isSimpleItem ? "qrc:/qml/SimpleItemEditor.qml" : "qrc:/qml/SurveyItemEditor.qml") : ""
+ property real availableWidth: _root.width - (_margin * 2) ///< How wide the editor should be
+ property var editorRoot: _root
}
-
- Component {
- id: simpleMissionItemEditor
-
- Item {
- id: innerItem
- width: availableWidth
- height: _showValues ? valuesLoader.y + valuesLoader.height : valuesLoader.y
-
- property bool _showValues: missionItem.isCurrentItem
-
- Loader {
- id: valuesLoader
- anchors.left: parent.left
- anchors.right: parent.right
- sourceComponent: _showValues ? valuesComponent : undefined
- }
- } // Item
- } // Component - simpleMissionItemEditor
-
- Component {
- id: complexMissionItemEditor
-
- Rectangle {
- id: outerRect
- height: editorColumn.height + (_margin * 2)
- width: availableWidth
- color: qgcPal.windowShadeDark
- radius: _radius
-
- property bool addPointsMode: false
-
- Column {
- id: editorColumn
- anchors.margins: _margin
- anchors.top: parent.top
- anchors.left: parent.left
- width: availableWidth
- spacing: _margin
-
- Connections {
- target: editorMap
-
- onMapClicked: {
- if (addPointsMode) {
- missionItem.addPolygonCoordinate(coordinate)
- }
- }
- }
-
- QGCLabel {
- text: "Fly a grid pattern over a defined area."
- wrapMode: Text.WordWrap
- }
-
- QGCButton {
- text: addPointsMode ? "Finished" : "Draw Polygon"
- onClicked: {
- if (addPointsMode) {
- addPointsMode = false
- } else {
- missionItem.clearPolygon()
- addPointsMode = true
- }
- }
- }
- }
- }
- } // Component - complexMissionItemEditor
-
} // Rectangle