diff --git a/src/MissionManager/PlanMasterController.cc b/src/MissionManager/PlanMasterController.cc index ebaeab53bf09c95f1192d3450397ad0b2606a416..35dd09717735f952838c735090af0da9df149b76 100644 --- a/src/MissionManager/PlanMasterController.cc +++ b/src/MissionManager/PlanMasterController.cc @@ -280,6 +280,7 @@ void PlanMasterController::loadFromFile(const QString& filename) return; } + QFileInfo fileInfo(filename); QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -288,8 +289,8 @@ void PlanMasterController::loadFromFile(const QString& filename) return; } - QString fileExtension(".%1"); - if (filename.endsWith(fileExtension.arg(AppSettings::planFileExtension))) { + bool success = false; + if(fileInfo.suffix() == AppSettings::planFileExtension) { QJsonDocument jsonDoc; QByteArray bytes = file.readAll(); @@ -319,17 +320,31 @@ void PlanMasterController::loadFromFile(const QString& filename) !_geoFenceController.load(json[_jsonGeoFenceObjectKey].toObject(), errorString) || !_rallyPointController.load(json[_jsonRallyPointsObjectKey].toObject(), errorString)) { qgcApp()->showMessage(errorMessage.arg(errorString)); + } else { + success = true; } - } else if (filename.endsWith(fileExtension.arg(AppSettings::missionFileExtension))) { + } else if (fileInfo.suffix() == AppSettings::missionFileExtension) { if (!_missionController.loadJsonFile(file, errorString)) { qgcApp()->showMessage(errorMessage.arg(errorString)); + } else { + success = true; } - } else if (filename.endsWith(fileExtension.arg(AppSettings::waypointsFileExtension)) || - filename.endsWith(fileExtension.arg(QStringLiteral("txt")))) { + } else if (fileInfo.suffix() == AppSettings::waypointsFileExtension || fileInfo.suffix() == QStringLiteral("txt")) { if (!_missionController.loadTextFile(file, errorString)) { qgcApp()->showMessage(errorMessage.arg(errorString)); + } else { + success = true; } + } else { + //-- TODO: What then? + } + + if(success){ + _currentPlanFile.sprintf("%s/%s.%s", fileInfo.path().toLocal8Bit().data(), fileInfo.completeBaseName().toLocal8Bit().data(), AppSettings::planFileExtension); + } else { + _currentPlanFile.clear(); } + emit currentPlanFileChanged(); if (!offline()) { setDirty(true); @@ -352,6 +367,14 @@ QJsonDocument PlanMasterController::saveToJson() return QJsonDocument(planJson); } +void +PlanMasterController::saveToCurrent() +{ + if(!_currentPlanFile.isEmpty()) { + saveToFile(_currentPlanFile); + } +} + void PlanMasterController::saveToFile(const QString& filename) { if (filename.isEmpty()) { @@ -367,9 +390,15 @@ void PlanMasterController::saveToFile(const QString& filename) if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qgcApp()->showMessage(tr("Plan save error %1 : %2").arg(filename).arg(file.errorString())); + _currentPlanFile.clear(); + emit currentPlanFileChanged(); } else { QJsonDocument saveDoc = saveToJson(); file.write(saveDoc.toJson()); + if(_currentPlanFile != planFilename) { + _currentPlanFile = planFilename; + emit currentPlanFileChanged(); + } } // Only clear dirty bit if we are offline @@ -411,6 +440,8 @@ void PlanMasterController::removeAll(void) _missionController.setDirty(false); _geoFenceController.setDirty(false); _rallyPointController.setDirty(false); + _currentPlanFile.clear(); + emit currentPlanFileChanged(); } } diff --git a/src/MissionManager/PlanMasterController.h b/src/MissionManager/PlanMasterController.h index 896349433893397d7af3591d3b607c018bd8524f..0878b2f202b50a004a9fd63b3fe741a657927b08 100644 --- a/src/MissionManager/PlanMasterController.h +++ b/src/MissionManager/PlanMasterController.h @@ -40,6 +40,7 @@ public: Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged) ///< true: Unsaved/sent changes are present, false: no changes since last save/send Q_PROPERTY(QString fileExtension READ fileExtension CONSTANT) ///< File extension for missions Q_PROPERTY(QString kmlFileExtension READ kmlFileExtension CONSTANT) + Q_PROPERTY(QString currentPlanFile READ currentPlanFile NOTIFY currentPlanFileChanged) ///< kml file extension for missions Q_PROPERTY(QStringList loadNameFilters READ loadNameFilters CONSTANT) ///< File filter list loading plan files Q_PROPERTY(QStringList saveNameFilters READ saveNameFilters CONSTANT) ///< File filter list saving plan files @@ -63,6 +64,7 @@ public: Q_INVOKABLE void loadFromVehicle(void); Q_INVOKABLE void sendToVehicle(void); Q_INVOKABLE void loadFromFile(const QString& filename); + Q_INVOKABLE void saveToCurrent(); Q_INVOKABLE void saveToFile(const QString& filename); Q_INVOKABLE void saveToKml(const QString& filename); Q_INVOKABLE void removeAll(void); ///< Removes all from controller only, synce required to remove from vehicle @@ -79,6 +81,7 @@ public: void setDirty (bool dirty); QString fileExtension (void) const; QString kmlFileExtension(void) const; + QString currentPlanFile (void) const { return _currentPlanFile; } QStringList loadNameFilters (void) const; QStringList saveNameFilters (void) const; QStringList fileKmlFilters (void) const; @@ -93,6 +96,7 @@ signals: void syncInProgressChanged (void); void dirtyChanged (bool dirty); void offlineChanged (bool offlineEditing); + void currentPlanFileChanged (); private slots: void _activeVehicleChanged(Vehicle* activeVehicle); @@ -118,6 +122,7 @@ private: bool _loadRallyPoints; bool _sendGeoFence; bool _sendRallyPoints; + QString _currentPlanFile; static const int _planFileVersion; static const char* _planFileType; diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index 278f56443172cfbab7887c4d7fcd96168069ade3..ac0d2adc246f41c1f2e8e935cb25e237879d3b47 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -524,37 +524,37 @@ QGCView { color: qgcPal.window title: qsTr("Plan") z: QGroundControl.zOrderWidgets - showAlternateIcon: [ false, false, false, masterController.dirty, false, false, false ] - rotateImage: [ false, false, false, masterController.syncInProgress, false, false, false ] - animateImage: [ false, false, false, masterController.dirty, false, false, false ] - buttonEnabled: [ true, true, true, !masterController.syncInProgress, true, true, true ] - buttonVisible: [ true, _waypointsOnlyMode, true, true, true, _showZoom, _showZoom ] + showAlternateIcon: [ masterController.dirty, false, false, false, false, false, false ] + rotateImage: [ masterController.syncInProgress, false, false, false, false, false, false ] + animateImage: [ masterController.dirty, false, false, false, false, false, false ] + buttonEnabled: [ !masterController.syncInProgress, true, true, true, true, true, true ] + buttonVisible: [ true, true, _waypointsOnlyMode, true, true, _showZoom, _showZoom ] maxHeight: mapScale.y - toolStrip.y property bool _showZoom: !ScreenTools.isMobile model: [ { - name: "Waypoint", - iconSource: "/qmlimages/MapAddMission.svg", - toggle: true + name: "File", + iconSource: "/qmlimages/MapSync.svg", + alternateIconSource: "/qmlimages/MapSyncChanged.svg", + dropPanelComponent: syncDropPanel + }, + { + name: "Waypoint", + iconSource: "/qmlimages/MapAddMission.svg", + toggle: true }, { - name: "ROI", - iconSource: "/qmlimages/MapAddMission.svg", - toggle: true + name: "ROI", + iconSource: "/qmlimages/MapAddMission.svg", + toggle: true }, { name: _singleComplexItem ? _missionController.complexMissionItemNames[0] : "Pattern", iconSource: "/qmlimages/MapDrawShape.svg", dropPanelComponent: _singleComplexItem ? undefined : patternDropPanel }, - { - name: "Sync", - iconSource: "/qmlimages/MapSync.svg", - alternateIconSource: "/qmlimages/MapSyncChanged.svg", - dropPanelComponent: syncDropPanel - }, { name: "Center", iconSource: "/qmlimages/MapCenter.svg", @@ -802,7 +802,7 @@ QGCView { Component { id: removeAllPromptDialog QGCViewMessage { - message: qsTr("Are you sure you want to remove all items? ") + + message: qsTr("Are you sure you want to remove all items and create a new plan? ") + (_planMasterController.offline ? "" : qsTr("This will also remove all items from the vehicle.")) function accept() { if (_planMasterController.offline) { @@ -815,6 +815,17 @@ QGCView { } } + Component { + id: clearVehicleMissionDialog + QGCViewMessage { + message: qsTr("Are you sure you want to remove all mission items and clear the mission from the vehicle?") + function accept() { + masterController.removeAllFromVehicle() + hideDialog() + } + } + } + //- ToolStrip DropPanel Components Component { @@ -863,8 +874,11 @@ QGCView { width: sendSaveGrid.width wrapMode: Text.WordWrap text: masterController.dirty ? - qsTr("You have unsaved changes. You should upload to your vehicle, or save to a file:") : - qsTr("Sync:") + (_activeVehicle ? + qsTr("You have unsaved changes. You should upload to your vehicle, or save to a file:") : + qsTr("You have unsaved changes.") + ) : + qsTr("Plan File:") } GridLayout { @@ -875,50 +889,50 @@ QGCView { columnSpacing: ScreenTools.defaultFontPixelWidth QGCButton { - text: qsTr("Upload") + text: qsTr("New...") Layout.fillWidth: true - enabled: !masterController.offline && !masterController.syncInProgress - onClicked: { + enabled: _visualItems.count > 1 + onClicked: { dropPanel.hide() - masterController.upload() + _qgcView.showDialog(removeAllPromptDialog, qsTr("New Plan"), _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No) } } QGCButton { - text: qsTr("Download") + text: qsTr("Open...") Layout.fillWidth: true - enabled: !masterController.offline && !masterController.syncInProgress + enabled: !masterController.syncInProgress onClicked: { dropPanel.hide() if (masterController.dirty) { - _qgcView.showDialog(syncLoadFromVehicleOverwrite, columnHolder._overwriteText, _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) + _qgcView.showDialog(syncLoadFromFileOverwrite, columnHolder._overwriteText, _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) } else { - masterController.loadFromVehicle() + masterController.loadFromSelectedFile() } } } QGCButton { - text: qsTr("Save Plan...") + text: qsTr("Save") Layout.fillWidth: true - enabled: !masterController.syncInProgress + enabled: !masterController.syncInProgress && masterController.currentPlanFile !== "" onClicked: { dropPanel.hide() - masterController.saveToSelectedFile() + if(masterController.currentPlanFile !== "") { + masterController.saveToCurrent() + } else { + masterController.saveToSelectedFile() + } } } QGCButton { - text: qsTr("Load Plan...") + text: qsTr("Save As...") Layout.fillWidth: true - enabled: !masterController.syncInProgress + enabled: !masterController.syncInProgress && _visualItems.count > 1 onClicked: { dropPanel.hide() - if (masterController.dirty) { - _qgcView.showDialog(syncLoadFromFileOverwrite, columnHolder._overwriteText, _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) - } else { - masterController.loadFromSelectedFile() - } + masterController.saveToSelectedFile() } } @@ -936,9 +950,9 @@ QGCView { QGCButton { text: qsTr("Save KML...") Layout.fillWidth: true - enabled: !masterController.syncInProgress + enabled: !masterController.syncInProgress && _visualItems.count > 1 onClicked: { - // First point do not count + // First point does not count if (_visualItems.count < 2) { _qgcView.showDialog(noItemForKML, qsTr("KML"), _qgcView.showDialogDefaultWidth, StandardButton.Cancel) return @@ -948,14 +962,54 @@ QGCView { } } + Rectangle { + width: parent.width * 0.8 + height: 1 + color: qgcPal.text + opacity: 0.5 + visible: !QGroundControl.corePlugin.options.disableVehicleConnection + Layout.fillWidth: true + Layout.columnSpan: 2 + } + QGCButton { - text: qsTr("Remove All") + text: qsTr("Upload") Layout.fillWidth: true - onClicked: { + enabled: !masterController.offline && !masterController.syncInProgress && _visualItems.count > 1 + visible: !QGroundControl.corePlugin.options.disableVehicleConnection + onClicked: { + dropPanel.hide() + masterController.upload() + } + } + + QGCButton { + text: qsTr("Download") + Layout.fillWidth: true + enabled: !masterController.offline && !masterController.syncInProgress + visible: !QGroundControl.corePlugin.options.disableVehicleConnection + onClicked: { + dropPanel.hide() + if (masterController.dirty) { + _qgcView.showDialog(syncLoadFromVehicleOverwrite, columnHolder._overwriteText, _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) + } else { + masterController.loadFromVehicle() + } + } + } + + QGCButton { + text: qsTr("Clear Vehicle Mission") + Layout.fillWidth: true + Layout.columnSpan: 2 + enabled: !masterController.offline && !masterController.syncInProgress + visible: !QGroundControl.corePlugin.options.disableVehicleConnection + onClicked: { dropPanel.hide() - _qgcView.showDialog(removeAllPromptDialog, qsTr("Remove all"), _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No) + _qgcView.showDialog(clearVehicleMissionDialog, text, _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel) } } + } } } diff --git a/src/QmlControls/DropPanel.qml b/src/QmlControls/DropPanel.qml index 91b1897e88b57c6cbd903fde159e7f046934e04b..b764d9fe4c3c9e331f1ae38c4a237405c0202070 100644 --- a/src/QmlControls/DropPanel.qml +++ b/src/QmlControls/DropPanel.qml @@ -71,11 +71,11 @@ Item { } function _calcPositions() { - var panelComponentWidth = panelLoader.item.width + var panelComponentWidth = panelLoader.item.width var panelComponentHeight = panelLoader.item.height - dropDownItem.width = panelComponentWidth + _dropMarginX2 + _arrowPointWidth - dropDownItem.height = panelComponentHeight + _dropMarginX2 + dropDownItem.width = panelComponentWidth + (_dropMarginX2 * 2) + _arrowPointWidth + dropDownItem.height = panelComponentHeight + (_dropMarginX2 * 2) dropDownItem.x = _dropEdgeTopPoint.x + _dropMargin dropDownItem.y = _dropEdgeTopPoint.y -(dropDownItem.height / 2) + radius diff --git a/src/QmlControls/ToolStrip.qml b/src/QmlControls/ToolStrip.qml index 23cbfdda5708e1b5a37a9de1f19c5dd8a1e00c05..10d6eba22ac32d44b79c2861f8f5160ceacc7153 100644 --- a/src/QmlControls/ToolStrip.qml +++ b/src/QmlControls/ToolStrip.qml @@ -16,7 +16,7 @@ import QGroundControl.Palette 1.0 Rectangle { id: _root color: qgcPal.window - width: ScreenTools.isMobile ? ScreenTools.minTouchPixels : ScreenTools.defaultFontPixelWidth * 6 + width: ScreenTools.isMobile ? ScreenTools.minTouchPixels : ScreenTools.defaultFontPixelWidth * 7 height: buttonStripColumn.height + (buttonStripColumn.anchors.margins * 2) radius: _radius border.width: 1 @@ -133,7 +133,7 @@ Rectangle { id: scope anchors.left: parent.left anchors.right: parent.right - height: width + height: width * 0.8 Rectangle { anchors.fill: parent @@ -141,9 +141,11 @@ Rectangle { QGCColoredImage { id: button - anchors.fill: parent + height: parent.height + width: height + anchors.centerIn: parent source: _source - sourceSize.height: parent.height + sourceSize.height: height fillMode: Image.PreserveAspectFit mipmap: true smooth: true diff --git a/src/api/QGCOptions.h b/src/api/QGCOptions.h index 56f0546829022dee0c3cb4319bc08f046eea03d1..213512981e5d39436055ee3c4824cc76ad3fdf12 100644 --- a/src/api/QGCOptions.h +++ b/src/api/QGCOptions.h @@ -49,6 +49,7 @@ public: Q_PROPERTY(bool guidedActionsRequireRCRSSI READ guidedActionsRequireRCRSSI CONSTANT) Q_PROPERTY(bool showMissionAbsoluteAltitude READ showMissionAbsoluteAltitude NOTIFY showMissionAbsoluteAltitudeChanged) Q_PROPERTY(bool showSimpleMissionStart READ showSimpleMissionStart NOTIFY showSimpleMissionStartChanged) + Q_PROPERTY(bool disableVehicleConnection READ disableVehicleConnection CONSTANT) /// Should QGC hide its settings menu and colapse it into one single menu (Settings and Vehicle Setup)? /// @return true if QGC should consolidate both menus into one. @@ -90,6 +91,7 @@ public: virtual bool showOfflineMapImport () const { return true; } virtual bool showMissionAbsoluteAltitude () const { return true; } virtual bool showSimpleMissionStart () const { return false; } + virtual bool disableVehicleConnection () const { return false; } ///< true: vehicle connection is disabled #if defined(__mobile__) virtual bool useMobileFileDialog () const { return true;}