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