Commit 62285253 authored by Don Gagne's avatar Don Gagne

More work on complex mission items

- Load/Save working
- Entry point working
parent 17fee7a5
...@@ -42,9 +42,12 @@ ...@@ -42,9 +42,12 @@
<file alias="PowerComponent.qml">src/AutoPilotPlugins/PX4/PowerComponent.qml</file> <file alias="PowerComponent.qml">src/AutoPilotPlugins/PX4/PowerComponent.qml</file>
<file alias="PowerComponentSummary.qml">src/AutoPilotPlugins/PX4/PowerComponentSummary.qml</file> <file alias="PowerComponentSummary.qml">src/AutoPilotPlugins/PX4/PowerComponentSummary.qml</file>
<file alias="PX4FlowSensor.qml">src/VehicleSetup/PX4FlowSensor.qml</file> <file alias="PX4FlowSensor.qml">src/VehicleSetup/PX4FlowSensor.qml</file>
<file alias="QGroundControl/Controls/qmldir">src/QmlControls/QGroundControl.Controls.qmldir</file>
<file alias="QGroundControl/Controls/ClickableColor.qml">src/QmlControls/ClickableColor.qml</file> <file alias="QGroundControl/Controls/ClickableColor.qml">src/QmlControls/ClickableColor.qml</file>
<file alias="QGroundControl/Controls/DropButton.qml">src/QmlControls/DropButton.qml</file> <file alias="QGroundControl/Controls/DropButton.qml">src/QmlControls/DropButton.qml</file>
<file alias="QGroundControl/Controls/ExclusiveGroupItem.qml">src/QmlControls/ExclusiveGroupItem.qml</file> <file alias="QGroundControl/Controls/ExclusiveGroupItem.qml">src/QmlControls/ExclusiveGroupItem.qml</file>
<file alias="QGroundControl/Controls/FactSliderPanel.qml">src/QmlControls/FactSliderPanel.qml</file>
<file alias="QGroundControl/Controls/IndicatorButton.qml">src/QmlControls/IndicatorButton.qml</file> <file alias="QGroundControl/Controls/IndicatorButton.qml">src/QmlControls/IndicatorButton.qml</file>
<file alias="QGroundControl/Controls/JoystickThumbPad.qml">src/QmlControls/JoystickThumbPad.qml</file> <file alias="QGroundControl/Controls/JoystickThumbPad.qml">src/QmlControls/JoystickThumbPad.qml</file>
<file alias="QGroundControl/Controls/MainToolBar.qml">src/ui/toolbar/MainToolBar.qml</file> <file alias="QGroundControl/Controls/MainToolBar.qml">src/ui/toolbar/MainToolBar.qml</file>
...@@ -73,14 +76,16 @@ ...@@ -73,14 +76,16 @@
<file alias="QGroundControl/Controls/QGCViewDialog.qml">src/QmlControls/QGCViewDialog.qml</file> <file alias="QGroundControl/Controls/QGCViewDialog.qml">src/QmlControls/QGCViewDialog.qml</file>
<file alias="QGroundControl/Controls/QGCViewMessage.qml">src/QmlControls/QGCViewMessage.qml</file> <file alias="QGroundControl/Controls/QGCViewMessage.qml">src/QmlControls/QGCViewMessage.qml</file>
<file alias="QGroundControl/Controls/QGCViewPanel.qml">src/QmlControls/QGCViewPanel.qml</file> <file alias="QGroundControl/Controls/QGCViewPanel.qml">src/QmlControls/QGCViewPanel.qml</file>
<file alias="QGroundControl/Controls/qmldir">src/QmlControls/QGroundControl.Controls.qmldir</file>
<file alias="QGroundControl/Controls/RoundButton.qml">src/QmlControls/RoundButton.qml</file> <file alias="QGroundControl/Controls/RoundButton.qml">src/QmlControls/RoundButton.qml</file>
<file alias="QGroundControl/Controls/SignalStrength.qml">src/ui/toolbar/SignalStrength.qml</file> <file alias="QGroundControl/Controls/SignalStrength.qml">src/ui/toolbar/SignalStrength.qml</file>
<file alias="QGroundControl/Controls/SubMenuButton.qml">src/QmlControls/SubMenuButton.qml</file> <file alias="QGroundControl/Controls/SubMenuButton.qml">src/QmlControls/SubMenuButton.qml</file>
<file alias="QGroundControl/Controls/VehicleRotationCal.qml">src/QmlControls/VehicleRotationCal.qml</file> <file alias="QGroundControl/Controls/VehicleRotationCal.qml">src/QmlControls/VehicleRotationCal.qml</file>
<file alias="QGroundControl/Controls/VehicleSummaryRow.qml">src/QmlControls/VehicleSummaryRow.qml</file> <file alias="QGroundControl/Controls/VehicleSummaryRow.qml">src/QmlControls/VehicleSummaryRow.qml</file>
<file alias="QGroundControl/Controls/ViewWidget.qml">src/ViewWidgets/ViewWidget.qml</file> <file alias="QGroundControl/Controls/ViewWidget.qml">src/ViewWidgets/ViewWidget.qml</file>
<file alias="QGroundControl/Controls/FactSliderPanel.qml">src/QmlControls/FactSliderPanel.qml</file>
<file alias="SimpleItemEditor.qml">src/MissionEditor/SimpleItemEditor.qml</file>
<file alias="SurveyItemEditor.qml">src/MissionEditor/SurveyItemEditor.qml</file>
<file alias="QGroundControl/FactControls/FactBitmask.qml">src/FactSystem/FactControls/FactBitmask.qml</file> <file alias="QGroundControl/FactControls/FactBitmask.qml">src/FactSystem/FactControls/FactBitmask.qml</file>
<file alias="QGroundControl/FactControls/FactCheckBox.qml">src/FactSystem/FactControls/FactCheckBox.qml</file> <file alias="QGroundControl/FactControls/FactCheckBox.qml">src/FactSystem/FactControls/FactCheckBox.qml</file>
<file alias="QGroundControl/FactControls/FactComboBox.qml">src/FactSystem/FactControls/FactComboBox.qml</file> <file alias="QGroundControl/FactControls/FactComboBox.qml">src/FactSystem/FactControls/FactComboBox.qml</file>
......
...@@ -82,7 +82,19 @@ bool JsonHelper::toQGeoCoordinate(const QJsonValue& jsonValue, QGeoCoordinate& c ...@@ -82,7 +82,19 @@ bool JsonHelper::toQGeoCoordinate(const QJsonValue& jsonValue, QGeoCoordinate& c
return true; return true;
} }
bool JsonHelper::validateKeyTypes(QJsonObject& jsonObject, const QStringList& keys, const QList<QJsonValue::Type>& 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<QJsonValue::Type>& types, QString& errorString)
{ {
for (int i=0; i<keys.count(); i++) { for (int i=0; i<keys.count(); i++) {
if (jsonObject.contains(keys[i])) { if (jsonObject.contains(keys[i])) {
......
...@@ -31,10 +31,12 @@ class JsonHelper ...@@ -31,10 +31,12 @@ class JsonHelper
{ {
public: public:
static bool validateRequiredKeys(const QJsonObject& jsonObject, const QStringList& keys, QString& errorString); static bool validateRequiredKeys(const QJsonObject& jsonObject, const QStringList& keys, QString& errorString);
static bool validateKeyTypes(QJsonObject& jsonObject, const QStringList& keys, const QList<QJsonValue::Type>& types, QString& errorString); static bool validateKeyTypes(const QJsonObject& jsonObject, const QStringList& keys, const QList<QJsonValue::Type>& types, QString& errorString);
static bool toQGeoCoordinate(const QJsonValue& jsonValue, QGeoCoordinate& coordinate, bool altitudeRequired, 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 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* _enumStringsJsonKey;
static const char* _enumValuesJsonKey; static const char* _enumValuesJsonKey;
}; };
......
...@@ -552,6 +552,7 @@ QGCView { ...@@ -552,6 +552,7 @@ QGCView {
var index = controller.insertComplexMissionItem(coordinate, controller.visualItems.count) var index = controller.insertComplexMissionItem(coordinate, controller.visualItems.count)
setCurrentItem(index) setCurrentItem(index)
checked = false checked = false
addMissionItemsButton.checked = false
} }
} }
......
...@@ -49,7 +49,6 @@ Rectangle { ...@@ -49,7 +49,6 @@ Rectangle {
property real _gradient: _statusValid ? Math.atan(_currentMissionItem.altDifference / _currentMissionItem.distance) : 0 property real _gradient: _statusValid ? Math.atan(_currentMissionItem.altDifference / _currentMissionItem.distance) : 0
property real _gradientPercent: isNaN(_gradient) ? 0 : _gradient * 100 property real _gradientPercent: isNaN(_gradient) ? 0 : _gradient * 100
property real _azimuth: _statusValid ? _currentMissionItem.azimuth : -1 property real _azimuth: _statusValid ? _currentMissionItem.azimuth : -1
property real _isHomePosition: _statusValid ? _currentMissionItem.homePosition : false
property bool _statusValid: currentMissionItem != undefined property bool _statusValid: currentMissionItem != undefined
property string _distanceText: _statusValid ? _distance.toFixed(2) + " m" : "" property string _distanceText: _statusValid ? _distance.toFixed(2) + " m" : ""
property string _altText: _statusValid ? _altDifference.toFixed(2) + " m" : "" property string _altText: _statusValid ? _altDifference.toFixed(2) + " m" : ""
......
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
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
}
}
}
}
}
/*=================================================================== /*===================================================================
QGroundControl Open Source Ground Control Station QGroundControl Open Source Ground Control Station
(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> (c) 2009, 2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project This file is part of the QGROUNDCONTROL project
...@@ -21,12 +21,25 @@ This file is part of the QGROUNDCONTROL project ...@@ -21,12 +21,25 @@ This file is part of the QGROUNDCONTROL project
======================================================================*/ ======================================================================*/
#include "ComplexMissionItem.h" #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) ComplexMissionItem::ComplexMissionItem(Vehicle* vehicle, QObject* parent)
: VisualMissionItem(vehicle, parent) : VisualMissionItem(vehicle, parent)
, _dirty(false) , _dirty(false)
{ {
MissionItem missionItem;
// FIXME: Bogus entries for testing
_missionItems += new MissionItem(this);
_missionItems += new MissionItem(this);
} }
ComplexMissionItem::ComplexMissionItem(const ComplexMissionItem& other, QObject* parent) ComplexMissionItem::ComplexMissionItem(const ComplexMissionItem& other, QObject* parent)
...@@ -36,20 +49,6 @@ ComplexMissionItem::ComplexMissionItem(const ComplexMissionItem& other, QObject* ...@@ -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) void ComplexMissionItem::clearPolygon(void)
{ {
_polygonPath.clear(); _polygonPath.clear();
...@@ -60,6 +59,11 @@ void ComplexMissionItem::addPolygonCoordinate(const QGeoCoordinate coordinate) ...@@ -60,6 +59,11 @@ void ComplexMissionItem::addPolygonCoordinate(const QGeoCoordinate coordinate)
{ {
_polygonPath << QVariant::fromValue(coordinate); _polygonPath << QVariant::fromValue(coordinate);
emit polygonPathChanged(); emit polygonPathChanged();
// FIXME: Hack, first polygon point sets entry coordinate
if (_polygonPath.count() == 1) {
setCoordinate(coordinate);
}
} }
int ComplexMissionItem::nextSequenceNumber(void) const int ComplexMissionItem::nextSequenceNumber(void) const
...@@ -72,6 +76,10 @@ void ComplexMissionItem::setCoordinate(const QGeoCoordinate& coordinate) ...@@ -72,6 +76,10 @@ void ComplexMissionItem::setCoordinate(const QGeoCoordinate& coordinate)
if (_coordinate != coordinate) { if (_coordinate != coordinate) {
_coordinate = coordinate; _coordinate = coordinate;
emit coordinateChanged(_coordinate); emit coordinateChanged(_coordinate);
_missionItems[0]->setCoordinate(coordinate);
// FIXME: Hack
_setExitCoordinate(coordinate);
} }
} }
...@@ -83,11 +91,148 @@ void ComplexMissionItem::setDirty(bool dirty) ...@@ -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<QGeoCoordinate>(), 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); // Clear old settings
Q_UNUSED(missionItems); 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<QJsonValue::Type> 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; i<polygonArray.count(); i++) {
const QJsonValue& pointValue = polygonArray[i];
QGeoCoordinate pointCoord;
if (!JsonHelper::toQGeoCoordinate(pointValue, pointCoord, false /* altitudeRequired */, errorString)) {
_clear();
return false;
}
_polygonPath << QVariant::fromValue(pointCoord);
}
// Internal mission items
QJsonArray itemArray(complexObject[MissionController::jsonSimpleItemsKey].toArray());
for (int i=0; i<itemArray.count(); i++) {
const QJsonValue& itemValue = itemArray[i];
if (!itemValue.isObject()) {
errorString = QStringLiteral("Mission item is not an object");
_clear();
return false;
}
MissionItem* item = new MissionItem(_vehicle, this);
if (item->load(itemValue.toObject(), errorString)) {
_missionItems.append(item);
} else {
_clear();
return false;
}
}
errorString = "Complex save NYI"; int itemCount = _missionItems.count();
return false; 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);
}
}
} }
/*===================================================================== /*=====================================================================
QGroundControl Open Source Ground Control Station QGroundControl Open Source Ground Control Station
(c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> (c) 2009 - 2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful, QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>. along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/ ======================================================================*/
#ifndef ComplexMissionItem_H #ifndef ComplexMissionItem_H
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
class ComplexMissionItem : public VisualMissionItem class ComplexMissionItem : public VisualMissionItem
{ {
Q_OBJECT Q_OBJECT
public: public:
ComplexMissionItem(Vehicle* vehicle, QObject* parent = NULL); ComplexMissionItem(Vehicle* vehicle, QObject* parent = NULL);
ComplexMissionItem(const ComplexMissionItem& other, QObject* parent = NULL); ComplexMissionItem(const ComplexMissionItem& other, QObject* parent = NULL);
...@@ -40,13 +40,19 @@ public: ...@@ -40,13 +40,19 @@ public:
Q_INVOKABLE void clearPolygon(void); Q_INVOKABLE void clearPolygon(void);
Q_INVOKABLE void addPolygonCoordinate(const QGeoCoordinate coordinate); Q_INVOKABLE void addPolygonCoordinate(const QGeoCoordinate coordinate);
QVariantList polygonPath(void); QVariantList polygonPath(void) { return _polygonPath; }
const QList<MissionItem*>& missionItems(void) { return _missionItems; } QList<MissionItem*>& missionItems(void) { return _missionItems; }
/// @return The next sequence number to use after this item. Takes into account child items of the complex item /// @return The next sequence number to use after this item. Takes into account child items of the complex item
int nextSequenceNumber(void) const; 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 // Overrides from VisualMissionItem
bool dirty (void) const final { return _dirty; } bool dirty (void) const final { return _dirty; }
...@@ -56,24 +62,36 @@ public: ...@@ -56,24 +62,36 @@ public:
QString commandDescription (void) const final { return "Survey"; } QString commandDescription (void) const final { return "Survey"; }
QString commandName (void) const final { return "Survey"; } QString commandName (void) const final { return "Survey"; }
QGeoCoordinate coordinate (void) const final { return _coordinate; } 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 coordinateHasRelativeAltitude (void) const final { return true; }
bool exitCoordinateHasRelativeAltitude (void) const final { return true; } bool exitCoordinateHasRelativeAltitude (void) const final { return true; }
bool exitCoordinateSameAsEntry (void) const final { return false; } bool exitCoordinateSameAsEntry (void) const final { return false; }
void setDirty (bool dirty) final; void setDirty (bool dirty) final;
void setCoordinate (const QGeoCoordinate& coordinate); void setCoordinate (const QGeoCoordinate& coordinate) final;
bool save (QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString) final; void setSequenceNumber (int sequenceNumber) final;
void save (QJsonObject& saveObject) const final;
signals: signals:
void polygonPathChanged(void); void polygonPathChanged(void);
private: private:
void _clear(void);
void _setExitCoordinate(const QGeoCoordinate& coordinate);
bool _dirty; bool _dirty;
QVariantList _polygonPath; QVariantList _polygonPath;
QList<MissionItem*> _missionItems; QList<MissionItem*> _missionItems;
QGeoCoordinate _coordinate; 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 #endif
...@@ -29,6 +29,7 @@ This file is part of the QGROUNDCONTROL project ...@@ -29,6 +29,7 @@ This file is part of the QGROUNDCONTROL project
#include "QGCApplication.h" #include "QGCApplication.h"
#include "SimpleMissionItem.h" #include "SimpleMissionItem.h"
#include "ComplexMissionItem.h" #include "ComplexMissionItem.h"
#include "JsonHelper.h"
#ifndef __mobile__ #ifndef __mobile__
#include "QGCFileDialog.h" #include "QGCFileDialog.h"
...@@ -36,11 +37,13 @@ This file is part of the QGROUNDCONTROL project ...@@ -36,11 +37,13 @@ This file is part of the QGROUNDCONTROL project
QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog") QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog")
const char* MissionController::jsonSimpleItemsKey = "items";
const char* MissionController::_settingsGroup = "MissionController"; const char* MissionController::_settingsGroup = "MissionController";
const char* MissionController::_jsonVersionKey = "version"; const char* MissionController::_jsonVersionKey = "version";
const char* MissionController::_jsonGroundStationKey = "groundStation"; const char* MissionController::_jsonGroundStationKey = "groundStation";
const char* MissionController::_jsonMavAutopilotKey = "MAV_AUTOPILOT"; const char* MissionController::_jsonMavAutopilotKey = "MAV_AUTOPILOT";
const char* MissionController::_jsonItemsKey = "items"; const char* MissionController::_jsonComplexItemsKey = "complexItems";
const char* MissionController::_jsonPlannedHomePositionKey = "plannedHomePosition"; const char* MissionController::_jsonPlannedHomePositionKey = "plannedHomePosition";
MissionController::MissionController(QObject *parent) MissionController::MissionController(QObject *parent)
...@@ -140,7 +143,9 @@ void MissionController::sendMissionItems(void) ...@@ -140,7 +143,9 @@ void MissionController::sendMissionItems(void)
missionItem.setSequenceNumber(sequenceNumber++); missionItem.setSequenceNumber(sequenceNumber++);
missionItems.append(&missionItem); missionItems.append(&missionItem);
} else { } else {
foreach (MissionItem* missionItem, qobject_cast<ComplexMissionItem*>(visualItem)->missionItems()) { ComplexMissionItem* complexItem = qobject_cast<ComplexMissionItem*>(visualItem);
for (int j=0; j<complexItem->missionItems().count(); j++) {
MissionItem* missionItem = complexItem->missionItems()[i];
missionItem->setSequenceNumber(sequenceNumber++); missionItem->setSequenceNumber(sequenceNumber++);
missionItems.append(missionItem); missionItems.append(missionItem);
} }
...@@ -247,29 +252,56 @@ bool MissionController::_loadJsonMissionFile(const QByteArray& bytes, QmlObjectL ...@@ -247,29 +252,56 @@ bool MissionController::_loadJsonMissionFile(const QByteArray& bytes, QmlObjectL
QJsonObject json = jsonDoc.object(); QJsonObject json = jsonDoc.object();
if (!json.contains(_jsonVersionKey)) { // Check for required keys
errorString = QStringLiteral("File is missing version key"); QStringList requiredKeys;
requiredKeys << _jsonVersionKey << _jsonPlannedHomePositionKey;
if (!JsonHelper::validateRequiredKeys(json, requiredKeys, errorString)) {
return false;
}
// Validate base key types
QStringList keyList;
QList<QJsonValue::Type> 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; return false;
} }
// Version check
if (json[_jsonVersionKey].toString() != "1.0") { if (json[_jsonVersionKey].toString() != "1.0") {
errorString = QStringLiteral("QGroundControl does not support this file version"); errorString = QStringLiteral("QGroundControl does not support this file version");
return false; return false;
} }
if (json.contains(_jsonItemsKey)) { // Simple items
if (!json[_jsonItemsKey].isArray()) { if (json.contains(jsonSimpleItemsKey)) {
errorString = QStringLiteral("items values must be array"); QJsonArray itemArray(json[jsonSimpleItemsKey].toArray());
return false; 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) { foreach (const QJsonValue& itemValue, itemArray) {
if (!itemValue.isObject()) { if (!itemValue.isObject()) {
errorString = QStringLiteral("Mission item is not an object"); errorString = QStringLiteral("Mission item is not an object");
return false; return false;
} }
SimpleMissionItem* item = new SimpleMissionItem(_activeVehicle, this); ComplexMissionItem* item = new ComplexMissionItem(_activeVehicle, this);
if (item->load(itemValue.toObject(), errorString)) { if (item->load(itemValue.toObject(), errorString)) {
missionItems->append(item); missionItems->append(item);
} else { } else {
...@@ -418,12 +450,12 @@ void MissionController::_saveMissionToFile(const QString& filename) ...@@ -418,12 +450,12 @@ void MissionController::_saveMissionToFile(const QString& filename)
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qgcApp()->showMessage(file.errorString()); qgcApp()->showMessage(file.errorString());
} else { } else {
QJsonObject missionObject; // top level json object QJsonObject missionFileObject; // top level json object
QJsonArray missionItemArray; // array of mission items QJsonArray simpleItemsObject;
QString errorString; QJsonArray complexItemsObject;
missionObject[_jsonVersionKey] = "1.0"; missionFileObject[_jsonVersionKey] = "1.0";
missionObject[_jsonGroundStationKey] = "QGroundControl"; missionFileObject[_jsonGroundStationKey] = "QGroundControl";
MAV_AUTOPILOT firmwareType = MAV_AUTOPILOT_GENERIC; MAV_AUTOPILOT firmwareType = MAV_AUTOPILOT_GENERIC;
if (_activeVehicle) { if (_activeVehicle) {
...@@ -435,7 +467,7 @@ void MissionController::_saveMissionToFile(const QString& filename) ...@@ -435,7 +467,7 @@ void MissionController::_saveMissionToFile(const QString& filename)
QSettings settings; QSettings settings;
firmwareType = (MAV_AUTOPILOT)settings.value("OfflineEditingFirmwareType", MAV_AUTOPILOT_ARDUPILOTMEGA).toInt(); firmwareType = (MAV_AUTOPILOT)settings.value("OfflineEditingFirmwareType", MAV_AUTOPILOT_ARDUPILOTMEGA).toInt();
} }
missionObject[_jsonMavAutopilotKey] = firmwareType; missionFileObject[_jsonMavAutopilotKey] = firmwareType;
// Save planned home position // Save planned home position
QJsonObject homePositionObject; QJsonObject homePositionObject;
...@@ -446,19 +478,26 @@ void MissionController::_saveMissionToFile(const QString& filename) ...@@ -446,19 +478,26 @@ void MissionController::_saveMissionToFile(const QString& filename)
qgcApp()->showMessage(QStringLiteral("Internal error: VisualMissionItem at index 0 not SimpleMissionItem")); qgcApp()->showMessage(QStringLiteral("Internal error: VisualMissionItem at index 0 not SimpleMissionItem"));
return; 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++) { for (int i=1; i<_visualItems->count(); i++) {
QJsonObject itemObject;
VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i)); VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
if (!visualItem->save(missionObject, missionItemArray, errorString)) { visualItem->save(itemObject);
qgcApp()->showMessage(errorString);
return; 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()); file.write(saveDoc.toJson());
} }
......
...@@ -75,6 +75,8 @@ public: ...@@ -75,6 +75,8 @@ public:
void setAutoSync(bool autoSync); void setAutoSync(bool autoSync);
bool syncInProgress(void); bool syncInProgress(void);
static const char* jsonSimpleItemsKey; ///< Key for simple items in a json file
signals: signals:
void visualItemsChanged(void); void visualItemsChanged(void);
void complexVisualItemsChanged(void); void complexVisualItemsChanged(void);
...@@ -131,7 +133,7 @@ private: ...@@ -131,7 +133,7 @@ private:
static const char* _jsonVersionKey; static const char* _jsonVersionKey;
static const char* _jsonGroundStationKey; static const char* _jsonGroundStationKey;
static const char* _jsonMavAutopilotKey; static const char* _jsonMavAutopilotKey;
static const char* _jsonItemsKey; static const char* _jsonComplexItemsKey;
static const char* _jsonPlannedHomePositionKey; static const char* _jsonPlannedHomePositionKey;
}; };
......
...@@ -148,7 +148,7 @@ MissionItem::~MissionItem() ...@@ -148,7 +148,7 @@ MissionItem::~MissionItem()
{ {
} }
void MissionItem::save(QJsonObject& json) void MissionItem::save(QJsonObject& json) const
{ {
json[_jsonTypeKey] = _itemType; json[_jsonTypeKey] = _itemType;
json[_jsonIdKey] = sequenceNumber(); json[_jsonIdKey] = sequenceNumber();
......
...@@ -103,7 +103,7 @@ public: ...@@ -103,7 +103,7 @@ public:
void setParam7 (double param7); void setParam7 (double param7);
void setCoordinate (const QGeoCoordinate& coordinate); void setCoordinate (const QGeoCoordinate& coordinate);
void save(QJsonObject& json); void save(QJsonObject& json) const;
bool load(QTextStream &loadStream); bool load(QTextStream &loadStream);
bool load(const QJsonObject& json, QString& errorString); bool load(const QJsonObject& json, QString& errorString);
......
...@@ -244,16 +244,9 @@ SimpleMissionItem::~SimpleMissionItem() ...@@ -244,16 +244,9 @@ SimpleMissionItem::~SimpleMissionItem()
{ {
} }
bool SimpleMissionItem::save(QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString) void SimpleMissionItem::save(QJsonObject& saveObject) const
{ {
Q_UNUSED(missionObject); _missionItem.save(saveObject);
Q_UNUSED(errorString);
QJsonObject itemObject;
_missionItem.save(itemObject);
missionItems.append(itemObject);
return true;
} }
bool SimpleMissionItem::load(QTextStream &loadStream) bool SimpleMissionItem::load(QTextStream &loadStream)
...@@ -585,3 +578,9 @@ void SimpleMissionItem::setCoordinate(const QGeoCoordinate& coordinate) ...@@ -585,3 +578,9 @@ void SimpleMissionItem::setCoordinate(const QGeoCoordinate& coordinate)
_missionItem.setCoordinate(coordinate); _missionItem.setCoordinate(coordinate);
} }
} }
void SimpleMissionItem::setSequenceNumber(int sequenceNumber)
{
_missionItem.setSequenceNumber(sequenceNumber);
VisualMissionItem::setSequenceNumber(sequenceNumber);
}
...@@ -107,8 +107,9 @@ public: ...@@ -107,8 +107,9 @@ public:
bool exitCoordinateSameAsEntry (void) const final { return true; } bool exitCoordinateSameAsEntry (void) const final { return true; }
void setDirty (bool dirty) final; void setDirty (bool dirty) final;
void setCoordinate (const QGeoCoordinate& coordinate); void setCoordinate (const QGeoCoordinate& coordinate) final;
bool save (QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString) final; void setSequenceNumber (int sequenceNumber) final;
void save (QJsonObject& saveObject) const final;
public slots: public slots:
void setDefaultsForCommand(void); void setDefaultsForCommand(void);
......
...@@ -108,10 +108,13 @@ public: ...@@ -108,10 +108,13 @@ public:
void setAltPercent (double altPercent); void setAltPercent (double altPercent);
void setAzimuth (double azimuth); void setAzimuth (double azimuth);
void setDistance (double distance); void setDistance (double distance);
void setSequenceNumber (int sequenceNumber);
Vehicle* vehicle(void) { return _vehicle; } 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 // Pure virtuals which must be provides by derived classes
virtual bool dirty (void) const = 0; virtual bool dirty (void) const = 0;
...@@ -130,11 +133,9 @@ public: ...@@ -130,11 +133,9 @@ public:
virtual void setDirty (bool dirty) = 0; virtual void setDirty (bool dirty) = 0;
virtual void setCoordinate (const QGeoCoordinate& coordinate) = 0; virtual void setCoordinate (const QGeoCoordinate& coordinate) = 0;
/// Save the item in Json format /// Save the item(s) in Json format
/// @param missionObject Top level object for entire mission file /// @param saveObject Save the item to this json object
/// @param missionItems Array of mission items virtual void save(QJsonObject& saveObject) const = 0;
/// @return false: save failed, errorString set
virtual bool save(QJsonObject& missionObject, QJsonArray& missionItems, QString& errorString) = 0;
signals: signals:
void altDifferenceChanged (double altDifference); void altDifferenceChanged (double altDifference);
......
...@@ -15,7 +15,7 @@ Rectangle { ...@@ -15,7 +15,7 @@ Rectangle {
id: _root id: _root
height: editorLoader.y + editorLoader.height + (_margin * 2) height: editorLoader.y + editorLoader.height + (_margin * 2)
color: missionItem.isCurrentItem ? qgcPal.buttonHighlight : qgcPal.windowShade color: _currentItem ? qgcPal.buttonHighlight : qgcPal.windowShade
radius: _radius radius: _radius
property var missionItem ///< MissionItem associated with this editor property var missionItem ///< MissionItem associated with this editor
...@@ -27,11 +27,12 @@ Rectangle { ...@@ -27,11 +27,12 @@ Rectangle {
signal insert(int i) signal insert(int i)
signal moveHomeToMapCenter signal moveHomeToMapCenter
property bool _currentItem: missionItem.isCurrentItem
property color _outerTextColor: _currentItem ? "black" : qgcPal.text
readonly property real _editFieldWidth: ScreenTools.defaultFontPixelWidth * 16 readonly property real _editFieldWidth: ScreenTools.defaultFontPixelWidth * 16
readonly property real _margin: ScreenTools.defaultFontPixelWidth / 2 readonly property real _margin: ScreenTools.defaultFontPixelWidth / 2
readonly property real _radius: ScreenTools.defaultFontPixelWidth / 2 readonly property real _radius: ScreenTools.defaultFontPixelWidth / 2
property color _outerTextColor: missionItem.isCurrentItem ? "black" : qgcPal.text
QGCPalette { QGCPalette {
id: qgcPal id: qgcPal
...@@ -88,7 +89,7 @@ Rectangle { ...@@ -88,7 +89,7 @@ Rectangle {
MenuItem { MenuItem {
text: "Show all values" text: "Show all values"
checkable: true checkable: true
checked: missionItem.rawEdit checked: missionItem.isSimpleItem ? missionItem.rawEdit : false
visible: missionItem.isSimpleItem visible: missionItem.isSimpleItem
onTriggered: { onTriggered: {
...@@ -142,191 +143,10 @@ Rectangle { ...@@ -142,191 +143,10 @@ Rectangle {
anchors.topMargin: _margin anchors.topMargin: _margin
anchors.left: parent.left anchors.left: parent.left
anchors.top: commandPicker.bottom anchors.top: commandPicker.bottom
sourceComponent: missionItem.isSimpleItem ? simpleMissionItemEditor : complexMissionItemEditor height: _currentItem && item ? item.height : 0
source: _currentItem ? (missionItem.isSimpleItem ? "qrc:/qml/SimpleItemEditor.qml" : "qrc:/qml/SurveyItemEditor.qml") : ""
/// 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
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 } // Rectangle
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment