From 63080c1e945fa1dbdaf5fa453f003bc180af36b7 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Fri, 25 Sep 2015 18:44:01 -0700 Subject: [PATCH] Support load/save Also understand the concept of missions which cannot be edited --- src/MissionEditor/MissionEditor.cc | 102 ++++++++++++++++++++++++++- src/MissionEditor/MissionEditor.h | 8 ++- src/MissionEditor/MissionEditor.qml | 32 ++++++++- src/MissionItem.cc | 40 ++++++++++- src/MissionItem.h | 6 ++ src/MissionManager/MissionManager.cc | 6 ++ src/MissionManager/MissionManager.h | 15 +++- 7 files changed, 200 insertions(+), 9 deletions(-) diff --git a/src/MissionEditor/MissionEditor.cc b/src/MissionEditor/MissionEditor.cc index c385d8416..4b04ade0f 100644 --- a/src/MissionEditor/MissionEditor.cc +++ b/src/MissionEditor/MissionEditor.cc @@ -25,6 +25,7 @@ This file is part of the QGROUNDCONTROL project #include "ScreenToolsController.h" #include "MultiVehicleManager.h" #include "MissionManager.h" +#include "QGCFileDialog.h" #include #include @@ -35,6 +36,7 @@ const char* MissionEditor::_settingsGroup = "MissionEditor"; MissionEditor::MissionEditor(QWidget *parent) : QGCQmlWidgetHolder(parent) , _missionItems(NULL) + , _canEdit(true) { // Get rid of layout default margins QLayout* pl = layout(); @@ -66,10 +68,14 @@ void MissionEditor::_newMissionItemsAvailable(void) _missionItems->deleteLater(); } - _missionItems = MultiVehicleManager::instance()->activeVehicle()->missionManager()->copyMissionItems(); + MissionManager* missionManager = MultiVehicleManager::instance()->activeVehicle()->missionManager(); + + _canEdit = missionManager->canEdit(); + _missionItems = missionManager->copyMissionItems(); _reSequence(); emit missionItemsChanged(); + emit canEditChanged(_canEdit); } void MissionEditor::getMissionItems(void) @@ -77,6 +83,8 @@ void MissionEditor::getMissionItems(void) Vehicle* activeVehicle = MultiVehicleManager::instance()->activeVehicle(); if (activeVehicle) { + MissionManager* missionManager = activeVehicle->missionManager(); + connect(missionManager, &MissionManager::newMissionItemsAvailable, this, &MissionEditor::_newMissionItemsAvailable); activeVehicle->missionManager()->requestMissionItems(); } } @@ -92,6 +100,10 @@ void MissionEditor::setMissionItems(void) int MissionEditor::addMissionItem(QGeoCoordinate coordinate) { + if (!_canEdit) { + qWarning() << "addMissionItem called with _canEdit == false"; + } + MissionItem * newItem = new MissionItem(this, _missionItems->count(), coordinate); if (_missionItems->count() == 0) { newItem->setCommand(MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF); @@ -111,12 +123,22 @@ void MissionEditor::_reSequence(void) void MissionEditor::removeMissionItem(int index) { + if (!_canEdit) { + qWarning() << "addMissionItem called with _canEdit == false"; + return; + } + _missionItems->removeAt(index); _reSequence(); } void MissionEditor::moveUp(int index) { + if (!_canEdit) { + qWarning() << "addMissionItem called with _canEdit == false"; + return; + } + if (_missionItems->count() < 2 || index <= 0 || index >= _missionItems->count()) { return; } @@ -135,6 +157,11 @@ void MissionEditor::moveUp(int index) void MissionEditor::moveDown(int index) { + if (!_canEdit) { + qWarning() << "addMissionItem called with _canEdit == false"; + return; + } + if (_missionItems->count() < 2 || index >= _missionItems->count() - 1) { return; } @@ -150,3 +177,76 @@ void MissionEditor::moveDown(int index) _reSequence(); } + +void MissionEditor::loadMissionFromFile(void) +{ + QString errorString; + QString filename = QGCFileDialog::getOpenFileName(NULL, "Select Mission File to load"); + + if (filename.isEmpty()) { + return; + } + + _missionItems->clear(); + _canEdit = true; + + QFile file(filename); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + errorString = file.errorString(); + } else { + QTextStream in(&file); + + const QStringList& version = in.readLine().split(" "); + + if (!(version.size() == 3 && version[0] == "QGC" && version[1] == "WPL" && version[2] == "120")) { + errorString = "The mission file is not compatible with the current version of QGroundControl."; + } else { + while (!in.atEnd()) { + MissionItem* item = new MissionItem(); + + if (item->load(in)) { + _missionItems->append(item); + + if (!item->canEdit()) { + _canEdit = false; + } + } else { + errorString = "The mission file is corrupted."; + break; + } + } + } + + } + + if (!errorString.isEmpty()) { + _missionItems->clear(); + } + + emit canEditChanged(_canEdit); +} + +void MissionEditor::saveMissionToFile(void) +{ + QString errorString; + QString filename = QGCFileDialog::getSaveFileName(NULL, "Select file to save mission to"); + + if (filename.isEmpty()) { + return; + } + + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + errorString = file.errorString(); + } else { + QTextStream out(&file); + + out << "QGC WPL 120\r\n"; // Version string + + for (int i=0; i<_missionItems->count(); i++) { + qobject_cast(_missionItems->get(i))->save(out); + } + } +} diff --git a/src/MissionEditor/MissionEditor.h b/src/MissionEditor/MissionEditor.h index 57786c002..dc8821826 100644 --- a/src/MissionEditor/MissionEditor.h +++ b/src/MissionEditor/MissionEditor.h @@ -35,11 +35,14 @@ public: MissionEditor(QWidget* parent = NULL); ~MissionEditor(); - Q_PROPERTY(QmlObjectListModel* missionItems READ missionItemsModel NOTIFY missionItemsChanged) + Q_PROPERTY(QmlObjectListModel* missionItems READ missionItemsModel NOTIFY missionItemsChanged) + Q_PROPERTY(bool canEdit READ canEdit NOTIFY canEditChanged) Q_INVOKABLE int addMissionItem(QGeoCoordinate coordinate); Q_INVOKABLE void getMissionItems(void); Q_INVOKABLE void setMissionItems(void); + Q_INVOKABLE void loadMissionFromFile(void); + Q_INVOKABLE void saveMissionToFile(void); Q_INVOKABLE void removeMissionItem(int index); Q_INVOKABLE void moveUp(int index); Q_INVOKABLE void moveDown(int index); @@ -47,9 +50,11 @@ public: // Property accessors QmlObjectListModel* missionItemsModel(void) { return _missionItems; } + bool canEdit(void) { return _canEdit; } signals: void missionItemsChanged(void); + void canEditChanged(bool canEdit); private slots: void _newMissionItemsAvailable(); @@ -59,6 +64,7 @@ private: private: QmlObjectListModel* _missionItems; + bool _canEdit; ///< true: UI can edit these items, false: can't edit, can only send to vehicle or save static const char* _settingsGroup; }; diff --git a/src/MissionEditor/MissionEditor.qml b/src/MissionEditor/MissionEditor.qml index f31bc443e..2e68f731d 100644 --- a/src/MissionEditor/MissionEditor.qml +++ b/src/MissionEditor/MissionEditor.qml @@ -40,8 +40,8 @@ QGCView { readonly property real _defaultLatitude: 37.803784 readonly property real _defaultLongitude: -122.462276 readonly property int _decimalPlaces: 7 - readonly property real _horizontalMargin: ScreenTools.defaultFontPixelWidth / 2 - readonly property real _verticalMargin: ScreenTools.defaultFontPixelHeight / 2 + readonly property real _horizontalMargin: ScreenTools.defaultFontPixelWidth / 2 + readonly property real _verticalMargin: ScreenTools.defaultFontPixelHeight / 2 readonly property var _activeVehicle: multiVehicleManager.activeVehicle property var _missionItems: controller.missionItems @@ -153,6 +153,20 @@ QGCView { onTriggered: controller.setMissionItems() } + + MenuSeparator { } + + MenuItem { + text: "Load mission from file..." + + onTriggered: controller.loadMissionFromFile() + } + + MenuItem { + text: "Save mission to file..." + + onTriggered: controller.saveMissionToFile() + } } } @@ -166,7 +180,7 @@ QGCView { anchors.bottom: parent.bottom spacing: _verticalMargin orientation: ListView.Vertical - model: controller.missionItems + model: controller.canEdit ? controller.missionItems : 0 property real _maxItemHeight: 0 @@ -201,6 +215,18 @@ QGCView { wrapMode: Text.WordWrap text: "Click in the map to add Mission Items" } + + QGCLabel { + anchors.topMargin: _verticalMargin + anchors.left: parent.left + anchors.right: parent.right + anchors.top: toolsButton.bottom + anchors.bottom: parent.bottom + visible: !controller.canEdit + wrapMode: Text.WordWrap + text: "The set of mission items you have loaded cannot be edited by QGroundControl. " + + "You will only be able to save these to a file, or send them to a vehicle." + } } // Item } // Rectangle - mission item list } // Item - split view container diff --git a/src/MissionItem.cc b/src/MissionItem.cc index 7399dd4c8..f633bb357 100644 --- a/src/MissionItem.cc +++ b/src/MissionItem.cc @@ -34,6 +34,7 @@ This file is part of the QGROUNDCONTROL project #include "MissionItem.h" +QGC_LOGGING_CATEGORY(MissionItemLog, "MissionItemLog") QDebug operator<<(QDebug dbg, const MissionItem& missionItem) { @@ -313,12 +314,20 @@ void MissionItem::setAction(int /*MAV_CMD*/ action) if (_command != action) { _command = (MavlinkQmlSingleton::Qml_MAV_CMD)action; - // Flick defaults according to WP type + // Fix defaults according to WP type if (_command == MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF) { // We default to 15 degrees minimum takeoff pitch setParam1(15.0); } + + if (specifiesCoordinate()) { + if (_frame != MAV_FRAME_GLOBAL && _frame != MAV_FRAME_GLOBAL_RELATIVE_ALT) { + setFrame(MAV_FRAME_GLOBAL_RELATIVE_ALT); + } + } else { + setFrame(MAV_FRAME_MISSION); + } emit changed(this); emit commandNameChanged(commandName()); @@ -758,3 +767,32 @@ void MissionItem::setCoordinate(const QGeoCoordinate& coordinate) setLongitude(coordinate.longitude()); setAltitude(coordinate.altitude()); } + +bool MissionItem::canEdit(void) +{ + bool found = false; + + for (int i=0; i<_cMavCmd2Name; i++) { + if (_rgMavCmd2Name[i].command == (MAV_CMD)_command) { + found = true; + break; + } + } + + if (found) { + if (!_autocontinue) { + qCDebug(MissionItemLog) << "canEdit false due to _autocontinue != true"; + return false; + } + + if (_frame != MAV_FRAME_GLOBAL && _frame != MAV_FRAME_GLOBAL_RELATIVE_ALT && _frame != MAV_FRAME_MISSION) { + qCDebug(MissionItemLog) << "canEdit false due unsupported frame type:" << _frame; + return false; + } + + return true; + } else { + qCDebug(MissionItemLog) << "canEdit false due unsupported command:" << _command; + return false; + } +} diff --git a/src/MissionItem.h b/src/MissionItem.h index 408743870..aae348b95 100644 --- a/src/MissionItem.h +++ b/src/MissionItem.h @@ -35,6 +35,9 @@ #include "MavlinkQmlSingleton.h" #include "QmlObjectListModel.h" #include "Fact.h" +#include "QGCLoggingCategory.h" + +Q_DECLARE_LOGGING_CATEGORY(MissionItemLog) class MissionItem : public QObject { @@ -104,6 +107,9 @@ public: void setYawDegrees(double yaw); // C++ only methods + + /// Returns true if this item can be edited in the ui + bool canEdit(void); double latitude(void) const { return _latitudeFact->value().toDouble(); } double longitude(void) const { return _longitudeFact->value().toDouble(); } diff --git a/src/MissionManager/MissionManager.cc b/src/MissionManager/MissionManager.cc index a282e59b9..765da693b 100644 --- a/src/MissionManager/MissionManager.cc +++ b/src/MissionManager/MissionManager.cc @@ -34,6 +34,7 @@ MissionManager::MissionManager(Vehicle* vehicle) : QThread() , _vehicle(vehicle) , _cMissionItems(0) + , _canEdit(true) , _ackTimeoutTimer(NULL) , _retryAck(AckNone) { @@ -231,6 +232,11 @@ void MissionManager::_handleMissionItem(const mavlink_message_t& message) missionItem.command); _missionItems.append(item); + if (!item->canEdit()) { + _canEdit = false; + emit canEditChanged(false); + } + int nextSequenceNumber = missionItem.seq + 1; if (nextSequenceNumber == _cMissionItems) { _sendTransactionComplete(); diff --git a/src/MissionManager/MissionManager.h b/src/MissionManager/MissionManager.h index 584f07b3a..57f85f15d 100644 --- a/src/MissionManager/MissionManager.h +++ b/src/MissionManager/MissionManager.h @@ -47,13 +47,17 @@ public: MissionManager(Vehicle* vehicle); ~MissionManager(); - Q_PROPERTY(bool inProgress READ inProgress CONSTANT) - Q_PROPERTY(QmlObjectListModel* missionItems READ missionItems CONSTANT) + Q_PROPERTY(bool inProgress READ inProgress CONSTANT) + Q_PROPERTY(QmlObjectListModel* missionItems READ missionItems CONSTANT) + Q_PROPERTY(bool canEdit READ canEdit NOTIFY canEditChanged) // Property accessors bool inProgress(void) { return _retryAck != AckNone; } QmlObjectListModel* missionItems(void) { return &_missionItems; } + bool canEdit(void) { return _canEdit; } + + // C++ methods void requestMissionItems(void); @@ -63,9 +67,13 @@ public: /// Returns a copy of the current set of mission items. Caller is responsible for /// freeing returned object. QmlObjectListModel* copyMissionItems(void); - + signals: + // Public signals + void canEditChanged(bool canEdit); void newMissionItemsAvailable(void); + + // Internal signals void _requestMissionItemsOnThread(void); void _writeMissionItemsOnThread(void); @@ -100,6 +108,7 @@ private: Vehicle* _vehicle; int _cMissionItems; ///< Mission items on vehicle + bool _canEdit; ///< true: Mission items are editable in the ui QTimer* _ackTimeoutTimer; AckType_t _retryAck; -- 2.22.0