diff --git a/src/FlightMap/Widgets/CenterMapDropPanel.qml b/src/FlightMap/Widgets/CenterMapDropPanel.qml index 8bfade62963770f9380733974bba619234b128df..2868ee5ac33ba2eba236d2430790b828c3c0c9ff 100644 --- a/src/FlightMap/Widgets/CenterMapDropPanel.qml +++ b/src/FlightMap/Widgets/CenterMapDropPanel.qml @@ -65,11 +65,11 @@ ColumnLayout { QGCButton { text: qsTr("Current Location") Layout.fillWidth: true - enabled: mainWindow.gcsPosition.isValid + enabled: _map.gcsPosition.isValid onClicked: { dropPanel.hide() - map.center = mainWindow.gcsPosition + map.center = _map.gcsPosition } } diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index a5fa8a49a600991833600dee8980d41a4681f5ee..489f92973a3260abbad961c7aaa8045dfb9840cf 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -56,6 +56,7 @@ MissionController::MissionController(QObject *parent) , _queuedSend(false) , _surveyMissionItemName(tr("Survey")) , _fwLandingMissionItemName(tr("Fixed Wing Landing")) + , _appSettings(qgcApp()->toolbox()->settingsManager()->appSettings()) { _missionFlightStatus.maxTelemetryDistance = 0; _missionFlightStatus.totalDistance = 0; @@ -449,18 +450,18 @@ bool MissionController::_loadJsonMissionFileV2(Vehicle* vehicle, const QJsonObje // Mission Settings QGeoCoordinate homeCoordinate; - SettingsManager* settingsManager = qgcApp()->toolbox()->settingsManager(); + AppSettings* appSettings = qgcApp()->toolbox()->settingsManager()->appSettings(); if (!JsonHelper::loadGeoCoordinate(json[_jsonPlannedHomePositionKey], true /* altitudeRequired */, homeCoordinate, errorString)) { return false; } if (json.contains(_jsonVehicleTypeKey) && vehicle->isOfflineEditingVehicle()) { - settingsManager->appSettings()->offlineEditingVehicleType()->setRawValue(json[_jsonVehicleTypeKey].toDouble()); + appSettings->offlineEditingVehicleType()->setRawValue(json[_jsonVehicleTypeKey].toDouble()); } if (json.contains(_jsonCruiseSpeedKey)) { - settingsManager->appSettings()->offlineEditingCruiseSpeed()->setRawValue(json[_jsonCruiseSpeedKey].toDouble()); + appSettings->offlineEditingCruiseSpeed()->setRawValue(json[_jsonCruiseSpeedKey].toDouble()); } if (json.contains(_jsonHoverSpeedKey)) { - settingsManager->appSettings()->offlineEditingHoverSpeed()->setRawValue(json[_jsonHoverSpeedKey].toDouble()); + appSettings->offlineEditingHoverSpeed()->setRawValue(json[_jsonHoverSpeedKey].toDouble()); } MissionSettingsItem* settingsItem = new MissionSettingsItem(vehicle, visualItems); @@ -647,17 +648,27 @@ void MissionController::loadFromFile(const QString& filename) _initAllVisualItems(); + // Split the filename into directory and filename + QString filenameOnly = filename; int lastSepIndex = filename.lastIndexOf(QStringLiteral("/")); if (lastSepIndex != -1) { filenameOnly = filename.right(filename.length() - lastSepIndex - 1); } + QString directoryOnly = filename.left(filename.length() - filenameOnly.length() - 1); + QString extension = AppSettings::missionFileExtension; if (filenameOnly.endsWith("." + extension)) { filenameOnly = filenameOnly.left(filenameOnly.length() - extension.length() - 1); } _settingsItem->missionName()->setRawValue(filenameOnly); + if (directoryOnly == qgcApp()->toolbox()->settingsManager()->appSettings()->missionSavePath()) { + QString emptyString; + _settingsItem->setLoadedMissionDirectory(emptyString); + } else { + _settingsItem->setLoadedMissionDirectory(directoryOnly); + } _settingsItem->setExistingMission(true); sendToVehicle(); @@ -704,8 +715,6 @@ bool MissionController::loadItemsFromFile(Vehicle* vehicle, const QString& filen void MissionController::saveToFile(const QString& filename) { - qDebug() << filename; - if (filename.isEmpty()) { return; } @@ -718,7 +727,7 @@ void MissionController::saveToFile(const QString& filename) QFile file(missionFilename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - qgcApp()->showMessage(file.errorString()); + qgcApp()->showMessage(tr("Mission save %1 : %2").arg(filename).arg(file.errorString())); } else { QJsonObject missionFileObject; // top level json object @@ -1596,9 +1605,17 @@ bool MissionController::missionInProgress(void) const void MissionController::save(void) { // Save to file if the mission is named - QString missionFullPath = _settingsItem->missionFullPath()->rawValue().toString(); - if (!missionFullPath.isEmpty()) { - saveToFile(missionFullPath); + + QString missionDir = _settingsItem->loadedMissionDirectory(); + if (missionDir.isEmpty()) { + missionDir = _appSettings->missionSavePath(); + } + + bool savedToFile = false; + QString missionName = _settingsItem->missionName()->rawValue().toString(); + if (!missionDir.isEmpty() && !missionName.isEmpty()) { + savedToFile = true; + saveToFile(missionDir + "/" + missionName); } // Send to vehicle if we are connected @@ -1606,11 +1623,22 @@ void MissionController::save(void) sendToVehicle(); } - _settingsItem->setExistingMission(_visualItems->count() > 1 && !missionFullPath.isEmpty()); + _settingsItem->setExistingMission(savedToFile); } void MissionController::clearMission(void) +{ + // We need to save the mission information around removeAll all since it delete/recreates settings item + QString missionName = _settingsItem->missionName()->rawValue().toString(); + QString loadedMissionDirectory = _settingsItem->loadedMissionDirectory(); + bool existingMission = _settingsItem->existingMission(); + removeAll(); + _settingsItem->missionName()->setRawValue(missionName); + _settingsItem->setLoadedMissionDirectory(loadedMissionDirectory); + _settingsItem->setExistingMission(existingMission); +} + +void MissionController::closeMission(void) { removeAll(); - save(); } diff --git a/src/MissionManager/MissionController.h b/src/MissionManager/MissionController.h index 3b06b6c170a434141c99f4b041322a26263a5b08..b179e6eed20a37c41618b49fa1e54b63b538c8fa 100644 --- a/src/MissionManager/MissionController.h +++ b/src/MissionManager/MissionController.h @@ -23,6 +23,7 @@ class CoordinateVector; class VisualMissionItem; class MissionItem; class MissionSettingsItem; +class AppSettings; Q_DECLARE_LOGGING_CATEGORY(MissionControllerLog) @@ -88,9 +89,15 @@ public: /// Sends the mission items to the specified vehicle static void sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems); + /// Saves the mission to file if any and sends to vehicle Q_INVOKABLE void save(void); + + /// Removes all items from the mission saving and sending as needed Q_INVOKABLE void clearMission(void); + /// Closes the mission, saving and sending as needed before closing + Q_INVOKABLE void closeMission(void); + // Overrides from PlanElementController void start (bool editMode) final; void startStaticActiveVehicle (Vehicle* vehicle) final; @@ -193,6 +200,7 @@ private: MissionFlightStatus_t _missionFlightStatus; QString _surveyMissionItemName; QString _fwLandingMissionItemName; + AppSettings* _appSettings; static const char* _settingsGroup; diff --git a/src/MissionManager/MissionSettingsItem.cc b/src/MissionManager/MissionSettingsItem.cc index 7081306e0a91de856050534a638235c3ef9ac8f8..f00af40fabb1e7ef094a9ec572b7e8475f7e65b1 100644 --- a/src/MissionManager/MissionSettingsItem.cc +++ b/src/MissionManager/MissionSettingsItem.cc @@ -24,7 +24,6 @@ QGC_LOGGING_CATEGORY(MissionSettingsComplexItemLog, "MissionSettingsComplexItemL const char* MissionSettingsItem::jsonComplexItemTypeValue = "MissionSettings"; const char* MissionSettingsItem::_missionNameName = "MissionName"; -const char* MissionSettingsItem::_missionFullPathName = "MissionFullPath"; const char* MissionSettingsItem::_plannedHomePositionAltitudeName = "PlannedHomePositionAltitude"; const char* MissionSettingsItem::_missionFlightSpeedName = "FlightSpeed"; const char* MissionSettingsItem::_missionEndActionName = "MissionEndAction"; @@ -36,7 +35,6 @@ MissionSettingsItem::MissionSettingsItem(Vehicle* vehicle, QObject* parent) , _existingMission(false) , _specifyMissionFlightSpeed(false) , _missionNameFact (0, _missionNameName, FactMetaData::valueTypeString) - , _missionFullPathFact (0, _missionFullPathName, FactMetaData::valueTypeString) , _plannedHomePositionAltitudeFact (0, _plannedHomePositionAltitudeName, FactMetaData::valueTypeDouble) , _missionFlightSpeedFact (0, _missionFlightSpeedName, FactMetaData::valueTypeDouble) , _missionEndActionFact (0, _missionEndActionName, FactMetaData::valueTypeUint32) @@ -65,8 +63,6 @@ MissionSettingsItem::MissionSettingsItem(Vehicle* vehicle, QObject* parent) setHomePositionSpecialCase(true); - connect(&_missionNameFact, &Fact::valueChanged, this, &MissionSettingsItem::_missionNameChanged); - connect(this, &MissionSettingsItem::specifyMissionFlightSpeedChanged, this, &MissionSettingsItem::_setDirtyAndUpdateLastSequenceNumber); connect(&_cameraSection, &CameraSection::missionItemCountChanged, this, &MissionSettingsItem::_setDirtyAndUpdateLastSequenceNumber); @@ -416,18 +412,6 @@ void MissionSettingsItem::_updateAltitudeInCoordinate(QVariant value) } } -void MissionSettingsItem::_missionNameChanged(QVariant value) -{ - QString missionDir = qgcApp()->toolbox()->settingsManager()->appSettings()->missionSavePath(); - QString missionName = value.toString(); - - if (missionName.isEmpty()) { - _missionFullPathFact.setRawValue(QString()); - } else { - _missionFullPathFact.setRawValue(missionDir + "/" + missionName); - } -} - void MissionSettingsItem::setExistingMission(bool existingMission) { if (existingMission != _existingMission) { @@ -435,3 +419,11 @@ void MissionSettingsItem::setExistingMission(bool existingMission) emit existingMissionChanged(existingMission ); } } + +void MissionSettingsItem::setLoadedMissionDirectory(QString& loadedMissionDirectory) +{ + if (_loadedMissionDirectory != loadedMissionDirectory) { + _loadedMissionDirectory = loadedMissionDirectory; + emit loadedMissionDirectoryChanged(loadedMissionDirectory); + } +} diff --git a/src/MissionManager/MissionSettingsItem.h b/src/MissionManager/MissionSettingsItem.h index 2ccb6343acc4310f695f1970c92095866f99555e..7333e46ff29db557ed102ff94e36d962be20eb3b 100644 --- a/src/MissionManager/MissionSettingsItem.h +++ b/src/MissionManager/MissionSettingsItem.h @@ -33,7 +33,7 @@ public: Q_ENUMS(MissionEndAction) Q_PROPERTY(Fact* missionName READ missionName CONSTANT) - Q_PROPERTY(Fact* missionFullPath READ missionFullPath CONSTANT) + Q_PROPERTY(QString loadedMissionDirectory READ loadedMissionDirectory WRITE setLoadedMissionDirectory NOTIFY loadedMissionDirectoryChanged) Q_PROPERTY(bool existingMission READ existingMission WRITE setExistingMission NOTIFY existingMissionChanged) Q_PROPERTY(bool specifyMissionFlightSpeed READ specifyMissionFlightSpeed WRITE setSpecifyMissionFlightSpeed NOTIFY specifyMissionFlightSpeedChanged) Q_PROPERTY(Fact* missionFlightSpeed READ missionFlightSpeed CONSTANT) @@ -42,7 +42,6 @@ public: Q_PROPERTY(QObject* cameraSection READ cameraSection CONSTANT) Fact* missionName (void) { return &_missionNameFact; } - Fact* missionFullPath (void) { return &_missionFullPathFact; } Fact* plannedHomePositionAltitude (void) { return &_plannedHomePositionAltitudeFact; } Fact* missionFlightSpeed (void) { return &_missionFlightSpeedFact; } Fact* missionEndAction (void) { return &_missionEndActionFact; } @@ -63,6 +62,10 @@ public: /// @return true: Mission end action was added bool addMissionEndAction(QList& items, int seqNum, QObject* missionItemParent); + // Returns the directory the misiosn was loaded from. Empty string is laoded from normal savePath. + QString loadedMissionDirectory(void) const { return _loadedMissionDirectory; } + void setLoadedMissionDirectory(QString& loadedMissionDirectory); + // Overrides from ComplexMissionItem double complexDistance (void) const final; @@ -102,23 +105,23 @@ public: signals: void specifyMissionFlightSpeedChanged(bool specifyMissionFlightSpeed); void existingMissionChanged(bool existingMission); + void loadedMissionDirectoryChanged(QString& loadedMissionDirectory); private slots: void _setDirtyAndUpdateLastSequenceNumber (void); void _setDirty (void); void _cameraSectionDirtyChanged (bool dirty); void _updateAltitudeInCoordinate (QVariant value); - void _missionNameChanged (QVariant value); private: bool _existingMission; bool _specifyMissionFlightSpeed; QGeoCoordinate _plannedHomePositionCoordinate; // Does not include altitde Fact _missionNameFact; - Fact _missionFullPathFact; Fact _plannedHomePositionAltitudeFact; Fact _missionFlightSpeedFact; Fact _missionEndActionFact; + QString _loadedMissionDirectory; CameraSection _cameraSection; int _sequenceNumber; @@ -127,7 +130,6 @@ private: static QMap _metaDataMap; static const char* _missionNameName; - static const char* _missionFullPathName; static const char* _plannedHomePositionAltitudeName; static const char* _missionFlightSpeedName; static const char* _missionEndActionName; diff --git a/src/PlanView/MissionItemEditor.qml b/src/PlanView/MissionItemEditor.qml index 7b0cb9c5f7831b83a70f92db758297b73f3e282c..65fb84a349b3cb6a34e29b044e72c06ba61fc980 100644 --- a/src/PlanView/MissionItemEditor.qml +++ b/src/PlanView/MissionItemEditor.qml @@ -12,8 +12,7 @@ import QGroundControl.Palette 1.0 /// Mission item edit control Rectangle { - id: _root - + id: _root height: editorLoader.y + editorLoader.height + (_margin * 2) color: _currentItem ? qgcPal.primaryButton : qgcPal.windowShade radius: _radius @@ -21,6 +20,7 @@ Rectangle { property var map ///< Map control property var missionItem ///< MissionItem associated with this editor property bool readOnly ///< true: read only view, false: full editing view + property var rootQgcView signal clicked signal remove diff --git a/src/PlanView/MissionSettingsEditor.qml b/src/PlanView/MissionSettingsEditor.qml index e0b61195d51eda84cbd78f8f35f70ea5fd2db066..d0050bdb4bbba947e6e7c551fcfc0a5d23d7dc7f 100644 --- a/src/PlanView/MissionSettingsEditor.qml +++ b/src/PlanView/MissionSettingsEditor.qml @@ -35,6 +35,7 @@ Rectangle { property bool _noMissionName: missionItem.missionName.valueString === "" property bool _showMissionList: _noMissionItemsAdded && (_noMissionName || _newMissionAlreadyExists) property bool _existingMissionLoaded: missionItem.existingMission + property var _appSettings: QGroundControl.settingsManager.appSettings readonly property string _firmwareLabel: qsTr("Firmware") readonly property string _vehicleLabel: qsTr("Vehicle") @@ -97,10 +98,36 @@ Rectangle { visible: !_existingMissionLoaded && _newMissionAlreadyExists } - QGCButton { - text: qsTr("Clear mission") - visible: !_noMissionItemsAdded - onClicked: missionController.clearMission() + FactCheckBox { + id: automaticUploadCheckbox + text: qsTr("Automatically upload on exit") + fact: _appSettings.automaticMissionUpload + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + + QGCButton { + text: qsTr("Clear") + visible: !_noMissionItemsAdded + Layout.fillWidth: true + onClicked: missionController.clearMission() + } + + QGCButton { + text: qsTr("Close") + visible: !_noMissionItemsAdded + Layout.fillWidth: true + onClicked: missionController.closeMission() + } + + QGCButton { + text: qsTr("Upload") + visible: !_noMissionItemsAdded && !automaticUploadCheckbox.checked + Layout.fillWidth: true + onClicked: missionController.sendToVehicle() + } } Loader { @@ -132,14 +159,36 @@ Rectangle { showSpacer: false } - QGCButton { + RowLayout { anchors.left: parent.left anchors.right: parent.right - text: qsTr("Load from Vehicle") - visible: !_offlineEditing - onClicked: { - missionController.loadFromVehicle() + QGCButton { + text: qsTr("From Vehicle") + visible: !_offlineEditing + Layout.fillWidth: true + onClicked: missionController.loadFromVehicle() + } + + QGCButton { + text: qsTr("Browse") + Layout.fillWidth: true + onClicked: fileDialog.openForLoad() + + QGCFileDialog { + id: fileDialog + qgcView: rootQgcView + title: qsTr("Select mission file") + selectExisting: true + folder: _appSettings.missionSavePath + fileExtension: _appSettings.missionFileExtension + nameFilters: [ qsTr("Mission Files (*.%1)").arg(fileExtension) , qsTr("All Files (*.*)") ] + + onAcceptedForLoad: { + missionController.loadFromFile(file) + fileDialog.close() + } + } } } @@ -337,5 +386,5 @@ Rectangle { } } } // Column - } + } // Deferred loader } // Rectangle diff --git a/src/PlanView/PlanToolBar.qml b/src/PlanView/PlanToolBar.qml index e9d66f8ab8c1db6332d072a57f4d3717726290ed..365558aef654a0d9abf0908a8cf13f4cfd51eed8 100644 --- a/src/PlanView/PlanToolBar.qml +++ b/src/PlanView/PlanToolBar.qml @@ -80,7 +80,6 @@ Rectangle { checked: false onClicked: { - console.log("Leave plan clicked") checked = false missionController.saveOnSwitch() showFlyView() diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index dc37ba6958422728ff9ebfd602dfb88fdd9e8d2e..9c764e9dcafe800663e9d1d1fe047ac5f08b2299 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -48,6 +48,7 @@ QGCView { property bool _lightWidgetBorders: editorMap.isSatelliteMap property bool _addWaypointOnClick: false property bool _singleComplexItem: missionController.complexMissionItemNames.length === 1 + property real _toolbarHeight: _qgcView.height - ScreenTools.availableHeight /// The controller which should be called for load/save, send to/from vehicle calls property var _syncDropDownController: missionController @@ -321,437 +322,421 @@ QGCView { anchors.left: parent.left anchors.right: parent.right - Item { - anchors.fill: parent + FlightMap { + id: editorMap + height: _qgcView.height + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right//rightPanel.left + mapName: "MissionEditor" - FlightMap { - id: editorMap - height: _qgcView.height - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - mapName: "MissionEditor" + // This is the center rectangle of the map which is not obscured by tools + property rect centerViewport: Qt.rect(_leftToolWidth, _toolbarHeight, editorMap.width - _leftToolWidth - _rightPanelWidth, editorMap.height - _statusHeight - _toolbarHeight) - // This is the center rectangle of the map which is not obscured by tools - property rect centerViewport: Qt.rect(_leftToolWidth, _toolbarHeight, editorMap.width - _leftToolWidth - _rightPanelWidth, editorMap.height - _statusHeight - _toolbarHeight) + property real _leftToolWidth: toolStrip.x + toolStrip.width + property real _statusHeight: waypointValuesDisplay.visible ? editorMap.height - waypointValuesDisplay.y : 0 - property real _toolbarHeight: _qgcView.height - ScreenTools.availableHeight - property real _leftToolWidth: toolStrip.x + toolStrip.width - property real _statusHeight: waypointValuesDisplay.visible ? editorMap.height - waypointValuesDisplay.y : 0 + readonly property real animationDuration: 500 - readonly property real animationDuration: 500 + // Initial map position duplicates Fly view position + Component.onCompleted: editorMap.center = QGroundControl.flightMapPosition - // Initial map position duplicates Fly view position - Component.onCompleted: editorMap.center = QGroundControl.flightMapPosition + Behavior on zoomLevel { + NumberAnimation { + duration: editorMap.animationDuration + easing.type: Easing.InOutQuad + } + } - Behavior on zoomLevel { - NumberAnimation { - duration: editorMap.animationDuration - easing.type: Easing.InOutQuad + QGCMapPalette { id: mapPal; lightColors: editorMap.isSatelliteMap } + + MouseArea { + //-- It's a whole lot faster to just fill parent and deal with top offset below + // than computing the coordinate offset. + anchors.fill: parent + onClicked: { + //-- Don't pay attention to items beneath the toolbar. + var topLimit = parent.height - ScreenTools.availableHeight + if(mouse.y < topLimit) { + return } - } - QGCMapPalette { id: mapPal; lightColors: editorMap.isSatelliteMap } + var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */) + coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) + coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces) + coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces) - MouseArea { - //-- It's a whole lot faster to just fill parent and deal with top offset below - // than computing the coordinate offset. - anchors.fill: parent - onClicked: { - //-- Don't pay attention to items beneath the toolbar. - var topLimit = parent.height - ScreenTools.availableHeight - if(mouse.y < topLimit) { - return + switch (_editingLayer) { + case _layerMission: + if (_addWaypointOnClick) { + insertSimpleMissionItem(coordinate, missionController.visualItems.count) } - - var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */) - coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) - coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces) - coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces) - - switch (_editingLayer) { - case _layerMission: - if (_addWaypointOnClick) { - insertSimpleMissionItem(coordinate, missionController.visualItems.count) - } - break - case _layerRallyPoints: - if (rallyPointController.rallyPointsSupported) { - rallyPointController.addPoint(coordinate) - } - break + break + case _layerRallyPoints: + if (rallyPointController.rallyPointsSupported) { + rallyPointController.addPoint(coordinate) } + break } } + } - // We use this item to support dragging since dragging a MapQuickItem just doesn't seem to work - Rectangle { - id: itemDragger - x: mapCoordinateIndicator ? (mapCoordinateIndicator.x + mapCoordinateIndicator.anchorPoint.x - (itemDragger.width / 2)) : 100 - y: mapCoordinateIndicator ? (mapCoordinateIndicator.y + mapCoordinateIndicator.anchorPoint.y - (itemDragger.height / 2)) : 100 - width: ScreenTools.defaultFontPixelHeight * 3 - height: ScreenTools.defaultFontPixelHeight * 3 - color: "transparent" - visible: false - z: QGroundControl.zOrderMapItems + 1 // Above item icons - - property var coordinateItem - property var mapCoordinateIndicator - property bool preventCoordinateBindingLoop: false - - onXChanged: liveDrag() - onYChanged: liveDrag() - - function liveDrag() { - if (!itemDragger.preventCoordinateBindingLoop && Drag.active) { - var point = Qt.point(itemDragger.x + (itemDragger.width / 2), itemDragger.y + (itemDragger.height / 2)) - var coordinate = editorMap.toCoordinate(point, false /* clipToViewPort */) - coordinate.altitude = itemDragger.coordinateItem.coordinate.altitude - itemDragger.preventCoordinateBindingLoop = true - itemDragger.coordinateItem.coordinate = coordinate - itemDragger.preventCoordinateBindingLoop = false - } - } - - function clearItem() { - itemDragger.visible = false - itemDragger.coordinateItem = undefined - itemDragger.mapCoordinateIndicator = undefined - } - - Drag.active: itemDrag.drag.active - Drag.hotSpot.x: width / 2 - Drag.hotSpot.y: height / 2 - - MouseArea { - id: itemDrag - anchors.fill: parent - drag.target: parent - drag.minimumX: 0 - drag.minimumY: 0 - drag.maximumX: itemDragger.parent.width - parent.width - drag.maximumY: itemDragger.parent.height - parent.height + // We use this item to support dragging since dragging a MapQuickItem just doesn't seem to work + Rectangle { + id: itemDragger + x: mapCoordinateIndicator ? (mapCoordinateIndicator.x + mapCoordinateIndicator.anchorPoint.x - (itemDragger.width / 2)) : 100 + y: mapCoordinateIndicator ? (mapCoordinateIndicator.y + mapCoordinateIndicator.anchorPoint.y - (itemDragger.height / 2)) : 100 + width: ScreenTools.defaultFontPixelHeight * 3 + height: ScreenTools.defaultFontPixelHeight * 3 + color: "transparent" + visible: false + z: QGroundControl.zOrderMapItems + 1 // Above item icons + + property var coordinateItem + property var mapCoordinateIndicator + property bool preventCoordinateBindingLoop: false + + onXChanged: liveDrag() + onYChanged: liveDrag() + + function liveDrag() { + if (!itemDragger.preventCoordinateBindingLoop && Drag.active) { + var point = Qt.point(itemDragger.x + (itemDragger.width / 2), itemDragger.y + (itemDragger.height / 2)) + var coordinate = editorMap.toCoordinate(point, false /* clipToViewPort */) + coordinate.altitude = itemDragger.coordinateItem.coordinate.altitude + itemDragger.preventCoordinateBindingLoop = true + itemDragger.coordinateItem.coordinate = coordinate + itemDragger.preventCoordinateBindingLoop = false } } - // Add the mission item visuals to the map - Repeater { - model: missionController.visualItems - - delegate: MissionItemMapVisual { - map: editorMap - onClicked: setCurrentItem(sequenceNumber) - } + function clearItem() { + itemDragger.visible = false + itemDragger.coordinateItem = undefined + itemDragger.mapCoordinateIndicator = undefined } - // Add lines between waypoints - MissionLineView { - model: _editingLayer == _layerMission ? missionController.waypointLines : undefined - } + Drag.active: itemDrag.drag.active + Drag.hotSpot.x: width / 2 + Drag.hotSpot.y: height / 2 - // Add the vehicles to the map - MapItemView { - model: QGroundControl.multiVehicleManager.vehicles - delegate: - VehicleMapItem { - vehicle: object - coordinate: object.coordinate - isSatellite: editorMap.isSatelliteMap - size: ScreenTools.defaultFontPixelHeight * 3 - z: QGroundControl.zOrderMapItems - 1 - } + MouseArea { + id: itemDrag + anchors.fill: parent + drag.target: parent + drag.minimumX: 0 + drag.minimumY: 0 + drag.maximumX: itemDragger.parent.width - parent.width + drag.maximumY: itemDragger.parent.height - parent.height } + } - // Plan Element selector (Mission/Fence/Rally) - Row { - id: planElementSelectorRow - anchors.top: toolStrip.top - anchors.leftMargin: parent.width - _rightPanelWidth - anchors.left: parent.left - z: QGroundControl.zOrderWidgets - spacing: _horizontalMargin - visible: false // WIP: Temporarily remove - QGroundControl.corePlugin.options.enablePlanViewSelector - - readonly property real _buttonRadius: ScreenTools.defaultFontPixelHeight * 0.75 - - ExclusiveGroup { - id: planElementSelectorGroup - onCurrentChanged: { - switch (current) { - case planElementMission: - _editingLayer = _layerMission - _syncDropDownController = missionController - break - case planElementGeoFence: - _editingLayer = _layerGeoFence - _syncDropDownController = geoFenceController - break - case planElementRallyPoints: - _editingLayer = _layerRallyPoints - _syncDropDownController = rallyPointController - break - } - _syncDropDownController.fitViewportToItems() - } - } - - QGCRadioButton { - id: planElementMission - exclusiveGroup: planElementSelectorGroup - text: qsTr("Mission") - checked: true - color: mapPal.text - textStyle: Text.Outline - textStyleColor: mapPal.textOutline - } - - Item { height: 1; width: 1 } - - QGCRadioButton { - id: planElementGeoFence - exclusiveGroup: planElementSelectorGroup - text: qsTr("Fence") - color: mapPal.text - textStyle: Text.Outline - textStyleColor: mapPal.textOutline - } - - Item { height: 1; width: 1 } - - QGCRadioButton { - id: planElementRallyPoints - exclusiveGroup: planElementSelectorGroup - text: qsTr("Rally") - color: mapPal.text - textStyle: Text.Outline - textStyleColor: mapPal.textOutline - } - } // Row - Plan Element Selector - - // Mission Item Editor - Item { - id: missionItemEditor - anchors.topMargin: planElementSelectorRow.visible ? _margin : 0 - anchors.top: planElementSelectorRow.visible ? planElementSelectorRow.bottom : planElementSelectorRow.top - anchors.bottom: parent.bottom - anchors.right: parent.right - width: _rightPanelWidth - opacity: _rightPanelOpacity - z: QGroundControl.zOrderTopMost - visible: _editingLayer == _layerMission - - MouseArea { - // This MouseArea prevents the Map below it from getting Mouse events. Without this - // things like mousewheel will scroll the Flickable and then scroll the map as well. - anchors.fill: missionItemEditorListView - onWheel: wheel.accepted = true - } - - QGCListView { - id: missionItemEditorListView - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - height: parent.height - spacing: _margin / 2 - orientation: ListView.Vertical - model: missionController.visualItems - cacheBuffer: Math.max(height * 2, 0) - clip: true - currentIndex: _currentMissionIndex - highlightMoveDuration: 250 - - delegate: MissionItemEditor { - map: editorMap - missionItem: object - width: parent.width - readOnly: false - - onClicked: setCurrentItem(object.sequenceNumber) - - onRemove: { - var removeIndex = index - itemDragger.clearItem() - missionController.removeMissionItem(removeIndex) - if (removeIndex >= missionController.visualItems.count) { - removeIndex-- - } - setCurrentItem(removeIndex) - } - - onInsert: insertSimpleMissionItem(editorMap.center, index) - } - } // QGCListView - } // Item - Mission Item editor - - // GeoFence Editor - Loader { - anchors.topMargin: _margin - anchors.top: planElementSelectorRow.bottom - anchors.right: parent.right - opacity: _rightPanelOpacity - z: QGroundControl.zOrderWidgets - sourceComponent: _editingLayer == _layerGeoFence ? geoFenceEditorComponent : undefined - - property real availableWidth: _rightPanelWidth - property real availableHeight: ScreenTools.availableHeight - property var myGeoFenceController: geoFenceController - } + // Add the mission item visuals to the map + Repeater { + model: missionController.visualItems - GeoFenceMapVisuals { - map: editorMap - myGeoFenceController: geoFenceController - interactive: _editingLayer == _layerGeoFence - homePosition: missionController.plannedHomePosition - planView: true + delegate: MissionItemMapVisual { + map: editorMap + onClicked: setCurrentItem(sequenceNumber) } + } - // Rally Point Editor - - RallyPointEditorHeader { - id: rallyPointHeader - anchors.topMargin: _margin - anchors.top: planElementSelectorRow.bottom - anchors.right: parent.right - width: _rightPanelWidth - opacity: _rightPanelOpacity - z: QGroundControl.zOrderTopMost - visible: _editingLayer == _layerRallyPoints - controller: rallyPointController - } + // Add lines between waypoints + MissionLineView { + model: _editingLayer == _layerMission ? missionController.waypointLines : undefined + } - RallyPointItemEditor { - id: rallyPointEditor - anchors.topMargin: _margin - anchors.top: rallyPointHeader.bottom - anchors.right: parent.right - width: _rightPanelWidth - opacity: _rightPanelOpacity - z: QGroundControl.zOrderTopMost - visible: _editingLayer == _layerRallyPoints && rallyPointController.points.count - rallyPoint: rallyPointController.currentRallyPoint - controller: rallyPointController + // Add the vehicles to the map + MapItemView { + model: QGroundControl.multiVehicleManager.vehicles + delegate: + VehicleMapItem { + vehicle: object + coordinate: object.coordinate + isSatellite: editorMap.isSatelliteMap + size: ScreenTools.defaultFontPixelHeight * 3 + z: QGroundControl.zOrderMapItems - 1 } + } + GeoFenceMapVisuals { + map: editorMap + myGeoFenceController: geoFenceController + interactive: _editingLayer == _layerGeoFence + homePosition: missionController.plannedHomePosition + planView: true + } - // Rally points on map + // Rally points on map - MapItemView { - model: rallyPointController.points + MapItemView { + model: rallyPointController.points - delegate: MapQuickItem { - id: itemIndicator - anchorPoint.x: sourceItem.anchorPointX - anchorPoint.y: sourceItem.anchorPointY - coordinate: object.coordinate - z: QGroundControl.zOrderMapItems + delegate: MapQuickItem { + id: itemIndicator + anchorPoint.x: sourceItem.anchorPointX + anchorPoint.y: sourceItem.anchorPointY + coordinate: object.coordinate + z: QGroundControl.zOrderMapItems - sourceItem: MissionItemIndexLabel { - id: itemIndexLabel - label: qsTr("R", "rally point map item label") - checked: _editingLayer == _layerRallyPoints ? object == rallyPointController.currentRallyPoint : false + sourceItem: MissionItemIndexLabel { + id: itemIndexLabel + label: qsTr("R", "rally point map item label") + checked: _editingLayer == _layerRallyPoints ? object == rallyPointController.currentRallyPoint : false - onClicked: rallyPointController.currentRallyPoint = object + onClicked: rallyPointController.currentRallyPoint = object - onCheckedChanged: { - if (checked) { - // Setup our drag item - itemDragger.visible = true - itemDragger.coordinateItem = Qt.binding(function() { return object }) - itemDragger.mapCoordinateIndicator = Qt.binding(function() { return itemIndicator }) - } + onCheckedChanged: { + if (checked) { + // Setup our drag item + itemDragger.visible = true + itemDragger.coordinateItem = Qt.binding(function() { return object }) + itemDragger.mapCoordinateIndicator = Qt.binding(function() { return itemIndicator }) } } } } + } - ToolStrip { - id: toolStrip - anchors.leftMargin: ScreenTools.defaultFontPixelWidth - anchors.left: parent.left - anchors.topMargin: _toolButtonTopMargin - anchors.top: parent.top - color: qgcPal.window - title: qsTr("Plan") - z: QGroundControl.zOrderWidgets - buttonEnabled: [ true, true, true, true, true ] - buttonVisible: [ true, true, true, _showZoom, _showZoom ] - maxHeight: mapScale.y - toolStrip.y - - property bool _showZoom: !ScreenTools.isMobile - - property bool mySingleComplexItem: _singleComplexItem - - model: [ - { - name: "Waypoint", - iconSource: "/qmlimages/MapAddMission.svg", - toggle: true - }, - { - name: "Pattern", - iconSource: "/qmlimages/MapDrawShape.svg", - dropPanelComponent: _singleComplexItem ? undefined : patternDropPanel - }, - { - name: "Center", - iconSource: "/qmlimages/MapCenter.svg", - dropPanelComponent: centerMapDropPanel - }, - { - name: "In", - iconSource: "/qmlimages/ZoomPlus.svg" - }, - { - name: "Out", - iconSource: "/qmlimages/ZoomMinus.svg" + ToolStrip { + id: toolStrip + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.topMargin: _toolButtonTopMargin + anchors.top: parent.top + color: qgcPal.window + title: qsTr("Plan") + z: QGroundControl.zOrderWidgets + buttonEnabled: [ true, true, true, true, true ] + buttonVisible: [ true, true, true, _showZoom, _showZoom ] + maxHeight: mapScale.y - toolStrip.y + + property bool _showZoom: !ScreenTools.isMobile + + property bool mySingleComplexItem: _singleComplexItem + + model: [ + { + name: "Waypoint", + iconSource: "/qmlimages/MapAddMission.svg", + toggle: true + }, + { + name: "Pattern", + iconSource: "/qmlimages/MapDrawShape.svg", + dropPanelComponent: _singleComplexItem ? undefined : patternDropPanel + }, + { + name: "Center", + iconSource: "/qmlimages/MapCenter.svg", + dropPanelComponent: centerMapDropPanel + }, + { + name: "In", + iconSource: "/qmlimages/ZoomPlus.svg" + }, + { + name: "Out", + iconSource: "/qmlimages/ZoomMinus.svg" + } + ] + + onClicked: { + switch (index) { + case 0: + _addWaypointOnClick = checked + break + case 1: + if (_singleComplexItem) { + addComplexItem(missionController.complexMissionItemNames[0]) } - ] + break + case 3: + editorMap.zoomLevel += 0.5 + break + case 4: + editorMap.zoomLevel -= 0.5 + break + } + } + } - onClicked: { - switch (index) { - case 0: - _addWaypointOnClick = checked - break - case 1: - if (_singleComplexItem) { - addComplexItem(missionController.complexMissionItemNames[0]) - } + MapScale { + id: mapScale + anchors.margins: ScreenTools.defaultFontPixelHeight * (0.66) + anchors.bottom: waypointValuesDisplay.visible ? waypointValuesDisplay.top : parent.bottom + anchors.left: parent.left + mapControl: editorMap + visible: !ScreenTools.isTinyScreen + } + + MissionItemStatus { + id: waypointValuesDisplay + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.bottom: parent.bottom + z: QGroundControl.zOrderTopMost + currentMissionItem: _currentMissionItem + missionItems: missionController.visualItems + expandedWidth: missionItemEditor.x - (ScreenTools.defaultFontPixelWidth * 2) + missionDistance: missionController.missionDistance + missionTime: missionController.missionTime + missionMaxTelemetry: missionController.missionMaxTelemetry + visible: _editingLayer == _layerMission && !ScreenTools.isShortScreen + } + } // FlightMap + + // Right pane for mission editing controls + Rectangle { + id: rightPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + width: _rightPanelWidth + color: qgcPal.window + opacity: 0.95 + + // Plan Element selector (Mission/Fence/Rally) + Row { + id: planElementSelectorRow + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: _horizontalMargin + visible: false // WIP: Temporarily remove - QGroundControl.corePlugin.options.enablePlanViewSelector + + readonly property real _buttonRadius: ScreenTools.defaultFontPixelHeight * 0.75 + + ExclusiveGroup { + id: planElementSelectorGroup + onCurrentChanged: { + switch (current) { + case planElementMission: + _editingLayer = _layerMission + _syncDropDownController = missionController break - case 3: - editorMap.zoomLevel += 0.5 + case planElementGeoFence: + _editingLayer = _layerGeoFence + _syncDropDownController = geoFenceController break - case 4: - editorMap.zoomLevel -= 0.5 + case planElementRallyPoints: + _editingLayer = _layerRallyPoints + _syncDropDownController = rallyPointController break } + _syncDropDownController.fitViewportToItems() } } - MapScale { - id: mapScale - anchors.margins: ScreenTools.defaultFontPixelHeight * (0.66) - anchors.bottom: waypointValuesDisplay.visible ? waypointValuesDisplay.top : parent.bottom - anchors.left: parent.left - mapControl: editorMap - visible: !ScreenTools.isTinyScreen + QGCRadioButton { + id: planElementMission + exclusiveGroup: planElementSelectorGroup + text: qsTr("Mission") + checked: true + color: mapPal.text + textStyle: Text.Outline + textStyleColor: mapPal.textOutline } - MissionItemStatus { - id: waypointValuesDisplay - anchors.margins: ScreenTools.defaultFontPixelWidth - anchors.left: parent.left - anchors.bottom: parent.bottom - z: QGroundControl.zOrderTopMost - currentMissionItem: _currentMissionItem - missionItems: missionController.visualItems - expandedWidth: missionItemEditor.x - (ScreenTools.defaultFontPixelWidth * 2) - missionDistance: missionController.missionDistance - missionTime: missionController.missionTime - missionMaxTelemetry: missionController.missionMaxTelemetry - visible: _editingLayer == _layerMission && !ScreenTools.isShortScreen + Item { height: 1; width: 1 } + + QGCRadioButton { + id: planElementGeoFence + exclusiveGroup: planElementSelectorGroup + text: qsTr("Fence") + color: mapPal.text + textStyle: Text.Outline + textStyleColor: mapPal.textOutline } - } // FlightMap - } // Item - split view container + + Item { height: 1; width: 1 } + + QGCRadioButton { + id: planElementRallyPoints + exclusiveGroup: planElementSelectorGroup + text: qsTr("Rally") + color: mapPal.text + textStyle: Text.Outline + textStyleColor: mapPal.textOutline + } + } // Row - Plan Element Selector + + // Mission Item Editor + Item { + id: missionItemEditor + anchors.top: planElementSelectorRow.visible ? planElementSelectorRow.bottom : planElementSelectorRow.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + visible: _editingLayer == _layerMission + + QGCListView { + id: missionItemEditorListView + anchors.fill: parent + spacing: _margin / 2 + orientation: ListView.Vertical + model: missionController.visualItems + cacheBuffer: Math.max(height * 2, 0) + clip: true + currentIndex: _currentMissionIndex + highlightMoveDuration: 250 + + delegate: MissionItemEditor { + map: editorMap + missionItem: object + width: parent.width + readOnly: false + rootQgcView: _qgcView + + onClicked: setCurrentItem(object.sequenceNumber) + + onRemove: { + var removeIndex = index + itemDragger.clearItem() + missionController.removeMissionItem(removeIndex) + if (removeIndex >= missionController.visualItems.count) { + removeIndex-- + } + setCurrentItem(removeIndex) + } + + onInsert: insertSimpleMissionItem(editorMap.center, index) + } + } // QGCListView + } // Item - Mission Item editor + + // GeoFence Editor + Loader { + anchors.top: planElementSelectorRow.visible ? planElementSelectorRow.bottom : planElementSelectorRow.top + anchors.left: parent.left + anchors.right: parent.right + sourceComponent: _editingLayer == _layerGeoFence ? geoFenceEditorComponent : undefined + + property real availableWidth: _rightPanelWidth + property real availableHeight: ScreenTools.availableHeight + property var myGeoFenceController: geoFenceController + } + + // Rally Point Editor + + RallyPointEditorHeader { + id: rallyPointHeader + anchors.top: planElementSelectorRow.visible ? planElementSelectorRow.bottom : planElementSelectorRow.top + anchors.left: parent.left + anchors.right: parent.right + visible: _editingLayer == _layerRallyPoints + controller: rallyPointController + } + + RallyPointItemEditor { + id: rallyPointEditor + anchors.top: planElementSelectorRow.visible ? planElementSelectorRow.bottom : planElementSelectorRow.top + anchors.left: parent.left + anchors.right: parent.right + visible: _editingLayer == _layerRallyPoints && rallyPointController.points.count + rallyPoint: rallyPointController.currentRallyPoint + controller: rallyPointController + } + } // Right panel } // QGCViewPanel Component { diff --git a/src/Settings/App.SettingsGroup.json b/src/Settings/App.SettingsGroup.json index 1c979df63a92856d93569d85780d843ef54eb3e4..0093e75fef8d010caad149886434f3853bd7220e 100644 --- a/src/Settings/App.SettingsGroup.json +++ b/src/Settings/App.SettingsGroup.json @@ -115,6 +115,13 @@ "type": "bool", "defaultValue": false }, +{ + "name": "AutomaticMissionUpload", + "shortDescription": "Automatic mission upload", + "longDescription": "Automatically upload mission to vehicle after changes", + "type": "bool", + "defaultValue": true +}, { "name": "SavePath", "shortDescription": "Application save directory", diff --git a/src/Settings/AppSettings.cc b/src/Settings/AppSettings.cc index 91db7e20fe960d8012ec9b7b273665795b6a7c71..ef027eeb62665293a7b07f91aadaff813ac661eb 100644 --- a/src/Settings/AppSettings.cc +++ b/src/Settings/AppSettings.cc @@ -31,6 +31,7 @@ const char* AppSettings::indoorPaletteName = "StyleIs const char* AppSettings::showLargeCompassName = "ShowLargeCompass"; const char* AppSettings::savePathName = "SavePath"; const char* AppSettings::autoLoadMissionsName = "AutoLoadMissions"; +const char* AppSettings::automaticMissionUploadName = "AutomaticMissionUpload"; const char* AppSettings::parameterFileExtension = "params"; const char* AppSettings::missionFileExtension = "mission"; @@ -59,6 +60,7 @@ AppSettings::AppSettings(QObject* parent) , _showLargeCompassFact(NULL) , _savePathFact(NULL) , _autoLoadMissionsFact(NULL) + , _automaticMissionUpload(NULL) { QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); qmlRegisterUncreatableType("QGroundControl.SettingsManager", 1, 0, "AppSettings", "Reference only"); @@ -276,3 +278,12 @@ Fact* AppSettings::autoLoadMissions(void) return _autoLoadMissionsFact; } +Fact* AppSettings::automaticMissionUpload(void) +{ + if (!_automaticMissionUpload) { + _automaticMissionUpload = _createSettingsFact(automaticMissionUploadName); + } + + return _automaticMissionUpload; +} + diff --git a/src/Settings/AppSettings.h b/src/Settings/AppSettings.h index feaa7dbd336b4fc261fa057a9376f67f431daa6b..0aeb61b3b1b6979d64b98e09013a238b4ad051e5 100644 --- a/src/Settings/AppSettings.h +++ b/src/Settings/AppSettings.h @@ -34,6 +34,7 @@ public: Q_PROPERTY(Fact* showLargeCompass READ showLargeCompass CONSTANT) Q_PROPERTY(Fact* savePath READ savePath CONSTANT) Q_PROPERTY(Fact* autoLoadMissions READ autoLoadMissions CONSTANT) + Q_PROPERTY(Fact* automaticMissionUpload READ automaticMissionUpload CONSTANT) Q_PROPERTY(QString missionSavePath READ missionSavePath NOTIFY savePathsChanged) Q_PROPERTY(QString parameterSavePath READ parameterSavePath NOTIFY savePathsChanged) @@ -58,6 +59,7 @@ public: Fact* showLargeCompass (void); Fact* savePath (void); Fact* autoLoadMissions (void); + Fact* automaticMissionUpload (void); QString missionSavePath (void); QString parameterSavePath (void); @@ -80,6 +82,7 @@ public: static const char* showLargeCompassName; static const char* savePathName; static const char* autoLoadMissionsName; + static const char* automaticMissionUploadName; // Application wide file extensions static const char* parameterFileExtension; @@ -116,6 +119,7 @@ private: SettingsFact* _showLargeCompassFact; SettingsFact* _savePathFact; SettingsFact* _autoLoadMissionsFact; + SettingsFact* _automaticMissionUpload; }; #endif