From 51a6792a00a882a34aad08c9212ca4a71735145b Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Wed, 28 Feb 2018 18:17:53 -0500 Subject: [PATCH] Handling Date/Time Flight management with date range, delete flights. --- src/Airmap/AirMapFlightPlanManager.cc | 224 +++++++++++++----- src/Airmap/AirMapFlightPlanManager.h | 26 +- src/Airmap/AirMapManager.cc | 4 +- src/Airmap/AirmapSettings.qml | 213 ++++++++++++++--- .../AirspaceFlightPlanProvider.cc | 38 +++ .../AirspaceFlightPlanProvider.h | 24 +- src/FactSystem/FactControls/FactTextField.qml | 2 +- 7 files changed, 423 insertions(+), 108 deletions(-) diff --git a/src/Airmap/AirMapFlightPlanManager.cc b/src/Airmap/AirMapFlightPlanManager.cc index 64fd33502..1691183db 100644 --- a/src/Airmap/AirMapFlightPlanManager.cc +++ b/src/Airmap/AirMapFlightPlanManager.cc @@ -35,6 +35,27 @@ AirMapFlightInfo::AirMapFlightInfo(const airmap::Flight& flight, QObject *parent } +//----------------------------------------------------------------------------- +QString +AirMapFlightInfo::createdTime() +{ + return QDateTime::fromMSecsSinceEpoch((quint64)airmap::milliseconds_since_epoch(_flight.created_at)).toString("yyyy MM dd - hh:mm:ss"); +} + +//----------------------------------------------------------------------------- +QString +AirMapFlightInfo::startTime() +{ + return QDateTime::fromMSecsSinceEpoch((quint64)airmap::milliseconds_since_epoch(_flight.start_time)).toString("yyyy MM dd - hh:mm:ss"); +} + +//----------------------------------------------------------------------------- +QString +AirMapFlightInfo::endTime() +{ + return QDateTime::fromMSecsSinceEpoch((quint64)airmap::milliseconds_since_epoch(_flight.end_time)).toString("yyyy MM dd - hh:mm:ss"); +} + //----------------------------------------------------------------------------- AirMapFlightPlanManager::AirMapFlightPlanManager(AirMapSharedState& shared, QObject *parent) : AirspaceFlightPlanProvider(parent) @@ -159,6 +180,113 @@ AirMapFlightPlanManager::updateFlightPlan() _updateFlightPlan(); } +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::deleteSelectedFlightPlans() +{ + qCDebug(AirMapManagerLog) << "Delete flight plan"; + _flightsToDelete.clear(); + for(int i = 0; i < _flightList.count(); i++) { + AirspaceFlightInfo* pInfo = _flightList.get(i); + if(pInfo && pInfo->selected()) { + _flightsToDelete << pInfo->flightPlanID(); + } + } + if(_flightsToDelete.count()) { + deleteFlightPlan(QString()); + } +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::deleteFlightPlan(QString flightPlanID) +{ + qCDebug(AirMapManagerLog) << "Delete flight plan"; + if(!flightPlanID.isEmpty()) { + _flightsToDelete.clear(); + _flightsToDelete << flightPlanID; + } + if (_pilotID == "") { + //-- Need to get the pilot id + qCDebug(AirMapManagerLog) << "Getting pilot ID"; + _state = State::GetPilotID; + std::weak_ptr isAlive(_instance); + _shared.doRequestWithLogin([this, isAlive](const QString& login_token) { + if (!isAlive.lock()) return; + Pilots::Authenticated::Parameters params; + params.authorization = login_token.toStdString(); + _shared.client()->pilots().authenticated(params, [this, isAlive](const Pilots::Authenticated::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::GetPilotID) return; + if (result) { + _pilotID = QString::fromStdString(result.value().id); + qCDebug(AirMapManagerLog) << "Got Pilot ID:"<<_pilotID; + _state = State::Idle; + _deleteFlightPlan(); + } else { + _state = State::Idle; + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to get pilot ID", QString::fromStdString(result.error().message()), description); + return; + } + }); + }); + } else { + _deleteFlightPlan(); + } +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_deleteFlightPlan() +{ + if(_flightsToDelete.count() < 1) { + qCDebug(AirMapManagerLog) << "Delete non existing flight plan"; + return; + } + if(_state != State::Idle) { + QTimer::singleShot(100, this, &AirMapFlightPlanManager::_deleteFlightPlan); + return; + } + int idx = _flightList.findFlightPlanID(_flightsToDelete.last()); + if(idx >= 0) { + AirspaceFlightInfo* pInfo = _flightList.get(idx); + if(pInfo) { + pInfo->setBeingDeleted(true); + } + } + qCDebug(AirMapManagerLog) << "Deleting flight plan:" << _flightsToDelete.last(); + _state = State::FlightDelete; + std::weak_ptr isAlive(_instance); + FlightPlans::Delete::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = _flightsToDelete.last().toStdString(); + //-- Delete flight plan + _shared.client()->flight_plans().delete_(params, [this, isAlive](const FlightPlans::Delete::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightDelete) return; + if (result) { + qCDebug(AirMapManagerLog) << "Flight plan deleted"; + _flightList.remove(_flightsToDelete.last()); + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Flight Plan deletion failed", QString::fromStdString(result.error().message()), description); + AirspaceFlightInfo* pFlight = _flightList.get(_flightList.findFlightPlanID(_flightsToDelete.last())); + if(pFlight) { + pFlight->setBeingDeleted(false); + } + } + _flightsToDelete.removeLast(); + //-- Keep at it until all flights are deleted + // TODO: This is ineficient. These whole airmapd transactions need to be moved into a separate + // worker thread. + if(_flightsToDelete.count()) { + QTimer::singleShot(10, this, &AirMapFlightPlanManager::_deleteFlightPlan); + } + _state = State::Idle; + }); +} + //----------------------------------------------------------------------------- bool AirMapFlightPlanManager::_collectFlightDtata() @@ -250,6 +378,10 @@ void AirMapFlightPlanManager::_uploadFlightPlan() { qCDebug(AirMapManagerLog) << "Uploading flight plan"; + if(_state != State::Idle) { + QTimer::singleShot(100, this, &AirMapFlightPlanManager::_uploadFlightPlan); + return; + } _state = State::FlightUpload; std::weak_ptr isAlive(_instance); _shared.doRequestWithLogin([this, isAlive](const QString& login_token) { @@ -262,16 +394,10 @@ AirMapFlightPlanManager::_uploadFlightPlan() params.latitude = _flight.takeoffCoord.latitude(); params.longitude = _flight.takeoffCoord.longitude(); params.pilot.id = _pilotID.toStdString(); - /* - - TODO: Convert this to fucking boost - - quint64 start = _flightStartTime.toUTC().toMSecsSinceEpoch(); - quint64 end = _flightEndTime.toUTC().toMSecsSinceEpoch(); - - */ - params.start_time = Clock::universal_time() + Minutes{5}; - params.end_time = Clock::universal_time() + Hours{2}; + quint64 start = _flightStartTime.toUTC().toMSecsSinceEpoch(); + quint64 end = _flightEndTime.toUTC().toMSecsSinceEpoch(); + params.start_time = airmap::from_milliseconds_since_epoch(airmap::Milliseconds{(long long)start}); + params.end_time = airmap::from_milliseconds_since_epoch(airmap::Milliseconds{(long long)end}); //-- Rules AirMapRulesetsManager* pRulesMgr = dynamic_cast(qgcApp()->toolbox()->airspaceManager()->ruleSets()); if(pRulesMgr) { @@ -345,12 +471,16 @@ AirMapFlightPlanManager::_updateFlightPlan() // little to do with those used when creating it. qCDebug(AirMapManagerLog) << "Updating flight plan"; + + if(_state != State::Idle) { + QTimer::singleShot(100, this, &AirMapFlightPlanManager::_updateFlightPlan); + return; + } //-- Get flight data if(!_collectFlightDtata()) { return; } - qCDebug(AirMapManagerLog) << "About to update the flight plan"; qCDebug(AirMapManagerLog) << "Takeoff: " << _flight.takeoffCoord; qCDebug(AirMapManagerLog) << "Bounding box:" << _flight.bc.pointNW << _flight.bc.pointSE; qCDebug(AirMapManagerLog) << "Flight Start:" << _flightStartTime; @@ -370,16 +500,10 @@ AirMapFlightPlanManager::_updateFlightPlan() params.flight_plan.buffer = 2.f; params.flight_plan.takeoff.latitude = _flight.takeoffCoord.latitude(); params.flight_plan.takeoff.longitude = _flight.takeoffCoord.longitude(); - /* - - TODO: Convert this to fucking boost - - quint64 start = _flightStartTime.toUTC().toMSecsSinceEpoch(); - quint64 end = _flightEndTime.toUTC().toMSecsSinceEpoch(); - - */ - params.flight_plan.start_time = Clock::universal_time() + Minutes{5}; - params.flight_plan.end_time = Clock::universal_time() + Hours{2}; + quint64 start = _flightStartTime.toUTC().toMSecsSinceEpoch(); + quint64 end = _flightEndTime.toUTC().toMSecsSinceEpoch(); + params.flight_plan.start_time = airmap::from_milliseconds_since_epoch(airmap::Milliseconds{(long long)start}); + params.flight_plan.end_time = airmap::from_milliseconds_since_epoch(airmap::Milliseconds{(long long)end}); //-- Rules /* AirMapRulesetsManager* pRulesMgr = dynamic_cast(qgcApp()->toolbox()->airspaceManager()->ruleSets()); @@ -566,36 +690,6 @@ AirMapFlightPlanManager::_pollBriefing() }); } -//----------------------------------------------------------------------------- -void -AirMapFlightPlanManager::_deleteFlightPlan() -{ - if(_flightPlan.isEmpty()) { - qCDebug(AirMapManagerLog) << "Delete non existing flight plan"; - return; - } - qCDebug(AirMapManagerLog) << "Deleting flight plan"; - _state = State::FlightDelete; - std::weak_ptr isAlive(_instance); - FlightPlans::Delete::Parameters params; - params.authorization = _shared.loginToken().toStdString(); - params.id = _flightPlan.toStdString(); - //-- Delete flight plan - _shared.client()->flight_plans().delete_(params, [this, isAlive](const FlightPlans::Delete::Result& result) { - if (!isAlive.lock()) return; - if (_state != State::FlightDelete) return; - if (result) { - _flightPlan.clear(); - qCDebug(AirMapManagerLog) << "Flight plan deleted"; - _state = State::Idle; - } else { - _state = State::Idle; - QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); - emit error("Flight Plan deletion failed", QString::fromStdString(result.error().message()), description); - } - }); -} - //----------------------------------------------------------------------------- void AirMapFlightPlanManager::_missionChanged() @@ -621,9 +715,16 @@ AirMapFlightPlanManager::_missionChanged() //----------------------------------------------------------------------------- void -AirMapFlightPlanManager::loadFlightList() +AirMapFlightPlanManager::loadFlightList(QDateTime startTime, QDateTime endTime) { - qCDebug(AirMapManagerLog) << "Search flights"; + //-- TODO: This is not checking if the state is Idle. Again, these need to + // queued up and handled by a worker thread. + qCDebug(AirMapManagerLog) << "Preparing load flight list"; + _loadingFlightList = true; + emit loadingFlightListChanged(); + _rangeStart = startTime; + _rangeEnd = endTime; + qCDebug(AirMapManagerLog) << "List flights from:" << _rangeStart.toString("yyyy MM dd - hh:mm:ss") << "to" << _rangeEnd.toString("yyyy MM dd - hh:mm:ss"); if (_pilotID == "") { //-- Need to get the pilot id qCDebug(AirMapManagerLog) << "Getting pilot ID"; @@ -645,6 +746,8 @@ AirMapFlightPlanManager::loadFlightList() _state = State::Idle; QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); emit error("Failed to get pilot ID", QString::fromStdString(result.error().message()), description); + _loadingFlightList = false; + emit loadingFlightListChanged(); return; } }); @@ -658,6 +761,11 @@ AirMapFlightPlanManager::loadFlightList() void AirMapFlightPlanManager::_loadFlightList() { + qCDebug(AirMapManagerLog) << "Load flight list"; + if(_state != State::Idle) { + QTimer::singleShot(100, this, &AirMapFlightPlanManager::_loadFlightList); + return; + } _flightList.clear(); emit flightListChanged(); _state = State::LoadFlightList; @@ -667,8 +775,13 @@ AirMapFlightPlanManager::_loadFlightList() if (_state != State::LoadFlightList) return; Flights::Search::Parameters params; params.authorization = login_token.toStdString(); - params.limit = 60; + quint64 start = _rangeStart.toUTC().toMSecsSinceEpoch(); + quint64 end = _rangeEnd.toUTC().toMSecsSinceEpoch(); + params.start_after = airmap::from_milliseconds_since_epoch(airmap::Milliseconds{(long long)start}); + params.start_before = airmap::from_milliseconds_since_epoch(airmap::Milliseconds{(long long)end}); + params.limit = 250; params.pilot_id = _pilotID.toStdString(); + qCDebug(AirMapManagerLog) << "List flights from:" << _rangeStart.toUTC().toString("yyyy MM dd - hh:mm:ss") << "to" << _rangeEnd.toUTC().toString("yyyy MM dd - hh:mm:ss"); _shared.client()->flights().search(params, [this, isAlive](const Flights::Search::Result& result) { if (!isAlive.lock()) return; if (_state != State::LoadFlightList) return; @@ -686,6 +799,9 @@ AirMapFlightPlanManager::_loadFlightList() emit error("Flight search failed", QString::fromStdString(result.error().message()), description); } } + _state = State::Idle; + _loadingFlightList = false; + emit loadingFlightListChanged(); }); }); } diff --git a/src/Airmap/AirMapFlightPlanManager.h b/src/Airmap/AirMapFlightPlanManager.h index 7cbaa56f6..4f834ae51 100644 --- a/src/Airmap/AirMapFlightPlanManager.h +++ b/src/Airmap/AirMapFlightPlanManager.h @@ -29,10 +29,10 @@ class AirMapFlightInfo : public AirspaceFlightInfo public: AirMapFlightInfo (const airmap::Flight& flight, QObject *parent = nullptr); QString flightID () override { return QString::fromStdString(_flight.id); } - QString flightPlanID () override { return QString::fromStdString(_flight.flight_plan_id.get()); } - QString createdTime () override { return QDateTime::currentDateTime().toString(Qt::SystemLocaleShortDate); } //-- TODO: Need to get rid of boost first - QString startTime () override { return QDateTime::currentDateTime().toString(Qt::SystemLocaleShortDate); } //-- TODO: Need to get rid of boost first - QString endTime () override { return QDateTime::currentDateTime().toString(Qt::SystemLocaleShortDate); } //-- TODO: Need to get rid of boost first + QString flightPlanID () override { return _flight.flight_plan_id ? QString::fromStdString(_flight.flight_plan_id.get()) : QString(); } + QString createdTime () override; + QString startTime () override; + QString endTime () override; QGeoCoordinate takeOff () override { return QGeoCoordinate(_flight.latitude, _flight.longitude);} QmlObjectListModel* boundingBox () override { return &_boundingBox; } private: @@ -66,13 +66,17 @@ public: QmlObjectListModel* rulesFollowing () override { return &_rulesFollowing; } QmlObjectListModel* briefFeatures () override { return &_briefFeatures; } AirspaceFlightModel*flightList () override { return &_flightList; } + bool loadingFlightList () override { return _loadingFlightList; } void updateFlightPlan () override; void submitFlightPlan () override; void startFlightPlanning (PlanMasterController* planController) override; void setFlightStartTime (QDateTime start) override; void setFlightEndTime (QDateTime end) override; - void loadFlightList () override; + void loadFlightList (QDateTime startTime, QDateTime endTime) override; + void deleteFlightPlan (QString flightPlanID) override; + + void deleteSelectedFlightPlans() override; signals: void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); @@ -80,14 +84,14 @@ signals: private slots: void _pollBriefing (); void _missionChanged (); - -private: + void _deleteFlightPlan (); void _uploadFlightPlan (); void _updateFlightPlan (); + void _loadFlightList (); + +private: void _createFlightPlan (); - void _deleteFlightPlan (); bool _collectFlightDtata (); - void _loadFlightList (); private: enum class State { @@ -121,8 +125,10 @@ private: QString _flightPlan; ///< Current flight plan QString _flightId; ///< Current flight ID, not necessarily accepted yet QString _pilotID; ///< Pilot ID in the form "auth0|abc123" + QStringList _flightsToDelete; PlanMasterController* _planController = nullptr; bool _valid = false; + bool _loadingFlightList = false; QDateTime _flightStartTime; QDateTime _flightEndTime; QmlObjectListModel _advisories; @@ -133,6 +139,8 @@ private: QmlObjectListModel _rulesFollowing; QmlObjectListModel _briefFeatures; AirspaceFlightModel _flightList; + QDateTime _rangeStart; + QDateTime _rangeEnd; AirspaceAdvisoryProvider::AdvisoryColor _airspaceColor; AirspaceFlightPlanProvider::PermitStatus _flightPermitStatus = AirspaceFlightPlanProvider::PermitNone; diff --git a/src/Airmap/AirMapManager.cc b/src/Airmap/AirMapManager.cc index a623fcd35..9020d99f2 100644 --- a/src/Airmap/AirMapManager.cc +++ b/src/Airmap/AirMapManager.cc @@ -37,8 +37,8 @@ AirMapManager::AirMapManager(QGCApplication* app, QGCToolbox* toolbox) { _logger = std::make_shared(); qt::register_types(); // TODO: still needed? - _logger->logging_category().setEnabled(QtDebugMsg, false); - _logger->logging_category().setEnabled(QtInfoMsg, false); + _logger->logging_category().setEnabled(QtDebugMsg, true); + _logger->logging_category().setEnabled(QtInfoMsg, true); _logger->logging_category().setEnabled(QtWarningMsg, true); _dispatchingLogger = std::make_shared(_logger); connect(&_shared, &AirMapSharedState::error, this, &AirMapManager::_error); diff --git a/src/Airmap/AirmapSettings.qml b/src/Airmap/AirmapSettings.qml index 33b696ec5..e9b2d0082 100644 --- a/src/Airmap/AirmapSettings.qml +++ b/src/Airmap/AirmapSettings.qml @@ -125,6 +125,7 @@ QGCView { QGCLabel { text: qsTr("Email:") } FactTextField { fact: _loginEmailFact + width: _editFieldWidth enabled: _airMapEnabled visible: _loginEmailFact.visible property Fact _loginEmailFact: QGroundControl.settingsManager.airMapSettings.loginEmail @@ -132,6 +133,7 @@ QGCView { QGCLabel { text: qsTr("Password:") } FactTextField { fact: _loginPasswordFact + width: _editFieldWidth enabled: _airMapEnabled visible: _loginPasswordFact.visible property Fact _loginPasswordFact: QGroundControl.settingsManager.airMapSettings.loginPassword @@ -225,13 +227,13 @@ QGCView { rowSpacing: ScreenTools.defaultFontPixelWidth * 0.25 anchors.centerIn: parent QGCLabel { text: qsTr("API Key:") } - FactTextField { fact: QGroundControl.settingsManager.airMapSettings.apiKey; } + FactTextField { fact: QGroundControl.settingsManager.airMapSettings.apiKey; width: _editFieldWidth; } QGCLabel { text: qsTr("Client ID:") } - FactTextField { fact: QGroundControl.settingsManager.airMapSettings.clientID; } + FactTextField { fact: QGroundControl.settingsManager.airMapSettings.clientID; width: _editFieldWidth; } QGCLabel { text: qsTr("User Name:") } - FactTextField { fact: QGroundControl.settingsManager.airMapSettings.userName; } + FactTextField { fact: QGroundControl.settingsManager.airMapSettings.userName; width: _editFieldWidth; } QGCLabel { text: qsTr("Password:") } - FactTextField { fact: QGroundControl.settingsManager.airMapSettings.password; echoMode: TextInput.Password } + FactTextField { fact: QGroundControl.settingsManager.airMapSettings.password; width: _editFieldWidth; echoMode: TextInput.Password } } } //----------------------------------------------------------------- @@ -283,16 +285,8 @@ QGCView { width: _qgcView.width height: _qgcView.height color: qgcPal.window - property var _flightList: QGroundControl.airspaceManager.flightPlan.flightList - Component.onCompleted: { - QGroundControl.airspaceManager.flightPlan.loadFlightList() - } - Connections { - target: _flightList - onCountChanged: { - tableView.resizeColumnsToContents() - } - } + property var _flightList: QGroundControl.airspaceManager.flightPlan.flightList + property real _mapWidth: ScreenTools.defaultFontPixelWidth * 40 MouseArea { anchors.fill: parent hoverEnabled: true @@ -300,6 +294,18 @@ QGCView { onPressed: { mouse.accepted = true; } onReleased: { mouse.accepted = true; } } + function updateSelection() { + //-- Clear selection + for(var i = 0; i < _flightList.count; i++) { + var o = _flightList.get(i) + if (o) o.selected = false + } + //-- Flag selected flights + tableView.selection.forEach(function(rowIndex){ + var o = _flightList.get(rowIndex) + if (o) o.selected = true + }) + } //--------------------------------------------------------- //-- Flight List RowLayout { @@ -312,8 +318,20 @@ QGCView { selectionMode: SelectionMode.MultiSelection Layout.fillWidth: true TableViewColumn { - title: qsTr("Created") - width: ScreenTools.defaultFontPixelWidth * 20 + title: qsTr("No") + width: ScreenTools.defaultFontPixelWidth * 3 + horizontalAlignment: Text.AlignHCenter + delegate : Text { + horizontalAlignment: Text.AlignHCenter + text: (styleData.row + 1) + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize + } + } + TableViewColumn { + title: qsTr("Created") + width: ScreenTools.defaultFontPixelWidth * 20 horizontalAlignment: Text.AlignHCenter delegate : Text { horizontalAlignment: Text.AlignHCenter @@ -321,11 +339,14 @@ QGCView { var o = _flightList.get(styleData.row) return o ? o.createdTime : "" } + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize } } TableViewColumn { - title: qsTr("Flight Start") - width: ScreenTools.defaultFontPixelWidth * 20 + title: qsTr("Flight Start") + width: ScreenTools.defaultFontPixelWidth * 20 horizontalAlignment: Text.AlignHCenter delegate : Text { horizontalAlignment: Text.AlignHCenter @@ -333,33 +354,126 @@ QGCView { var o = _flightList.get(styleData.row) return o ? o.startTime : "" } + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize } } TableViewColumn { - title: qsTr("Take Off") - width: ScreenTools.defaultFontPixelWidth * 22 + title: qsTr("State") + width: ScreenTools.defaultFontPixelWidth * 22 horizontalAlignment: Text.AlignHCenter delegate : Text { horizontalAlignment: Text.AlignHCenter text: { var o = _flightList.get(styleData.row) - return o ? o.takeOff.latitude.toFixed(6) + ', ' + o.takeOff.longitude.toFixed(6) : "" + return o ? (o.beingDeleted ? qsTr("Deleting") : qsTr("Valid")) : qsTr("Unknown") } + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize } } } Item { - width: map.width + width: flightListRoot._mapWidth height: parent.height Layout.alignment: Qt.AlignTop | Qt.AlignLeft + QGCLabel { + id: loadingLabel + text: qsTr("Loading Flight List") + visible: QGroundControl.airspaceManager.flightPlan.loadingFlightList + anchors.centerIn: parent + } + QGCColoredImage { + id: busyIndicator + height: ScreenTools.defaultFontPixelHeight * 2.5 + width: height + source: "/qmlimages/MapSync.svg" + sourceSize.height: height + fillMode: Image.PreserveAspectFit + mipmap: true + smooth: true + color: qgcPal.colorGreen + visible: loadingLabel.visible + anchors.top: loadingLabel.bottom + anchors.topMargin: ScreenTools.defaultFontPixelHeight + anchors.horizontalCenter: parent.horizontalCenter + RotationAnimation on rotation { + loops: Animation.Infinite + from: 360 + to: 0 + duration: 740 + running: busyIndicator.visible + } + } Column { - spacing: ScreenTools.defaultFontPixelHeight + spacing: ScreenTools.defaultFontPixelHeight * 0.75 + visible: !QGroundControl.airspaceManager.flightPlan.loadingFlightList anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter QGCLabel { text: qsTr("Flight List") anchors.horizontalCenter: parent.horizontalCenter } + Rectangle { + color: qgcPal.window + border.color: qgcPal.globalTheme === QGCPalette.Dark ? Qt.rgba(1,1,1,0.25) : Qt.rgba(0,0,0,0.25) + border.width: 1 + radius: 4 + width: _mapWidth - (ScreenTools.defaultFontPixelWidth * 2) + height: rangeCol.height + (ScreenTools.defaultFontPixelHeight * 2) + Column { + id: rangeCol + anchors.centerIn: parent + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + QGCLabel { + text: qsTr("Range") + anchors.horizontalCenter: parent.horizontalCenter + } + Row { + spacing: ScreenTools.defaultFontPixelWidth * 2 + anchors.horizontalCenter: parent.horizontalCenter + Column { + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + QGCButton { + text: qsTr("From") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: _buttonWidth * 0.5 + onClicked: fromPicker.visible = true + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + anchors.horizontalCenter: parent.horizontalCenter + text: fromPicker.selectedDate.toLocaleDateString(Qt.locale()) + } + } + Rectangle { + width: 1 + height: parent.height + color: qgcPal.globalTheme === QGCPalette.Dark ? Qt.rgba(1,1,1,0.25) : Qt.rgba(0,0,0,0.25) + } + Column { + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + QGCButton { + text: qsTr("To") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: _buttonWidth * 0.5 + onClicked: toPicker.visible = true + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + anchors.horizontalCenter: parent.horizontalCenter + text: toPicker.selectedDate.toLocaleDateString(Qt.locale()) + } + } + } + } + } QGCButton { text: qsTr("Refresh") backRadius: 4 @@ -369,7 +483,11 @@ QGCView { enabled: true anchors.horizontalCenter: parent.horizontalCenter onClicked: { - QGroundControl.airspaceManager.flightPlan.loadFlightList() + var start = fromPicker.selectedDate + var end = toPicker.selectedDate + start.setHours(0,0,0,0) + end.setHours(23,59,59,0) + QGroundControl.airspaceManager.flightPlan.loadFlightList(start, end) } } QGCButton { @@ -402,20 +520,11 @@ QGCView { heightFactor: 0.3333 showBorder: true width: _buttonWidth - enabled: false + enabled: tableView.selection.count > 0 anchors.horizontalCenter: parent.horizontalCenter onClicked: { - //-- Clear selection - for(var i = 0; i < _flightList.count; i++) { - var o = _flightList.get(i) - if (o) o.selected = false - } - //-- Flag selected flights - tableView.selection.forEach(function(rowIndex){ - var o = _flightList.get(rowIndex) - if (o) o.selected = true - }) - //TODO: + flightListRoot.updateSelection(); + QGroundControl.airspaceManager.flightPlan.deleteSelectedFlightPlans() } } QGCButton { @@ -429,9 +538,21 @@ QGCView { panelLoader.sourceComponent = null } } + QGCLabel { + text: _flightList.count > 0 ? tableView.selection.count + '/' + _flightList.count + qsTr(" Flights Selected") : qsTr("No Flights Loaded") + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: qsTr("A maximum of 250 flights were loaded") + color: qgcPal.colorOrange + font.pixelSize: ScreenTools.smallFontPointSize + visible: _flightList.count >= 250 + anchors.horizontalCenter: parent.horizontalCenter + } } QGCLabel { - text: qsTr("Flight Area") + text: qsTr("Flight Area ") + (tableView.currentRow + 1) + visible: !QGroundControl.airspaceManager.flightPlan.loadingFlightList && _flightList.count > 0 && tableView.currentRow >= 0 anchors.bottom: map.top anchors.bottomMargin: ScreenTools.defaultFontPixelHeight * 0.25 anchors.horizontalCenter: parent.horizontalCenter @@ -446,6 +567,7 @@ QGCView { center: QGroundControl.flightMapPosition gesture.acceptedGestures: MapGestureArea.PinchGesture plugin: Plugin { name: "QGroundControl" } + visible: !QGroundControl.airspaceManager.flightPlan.loadingFlightList && _flightList.count > 0 && tableView.currentRow >= 0 function updateActiveMapType() { var settings = QGroundControl.settingsManager.flightMapSettings var fullMapName = settings.mapProvider.enumStringValue + " " + settings.mapType.enumStringValue @@ -470,6 +592,23 @@ QGCView { } } + Calendar { + id: fromPicker + anchors.centerIn: parent + visible: false; + onClicked: { + visible = false; + } + } + Calendar { + id: toPicker + anchors.centerIn: parent + visible: false; + minimumDate: fromPicker.selectedDate + onClicked: { + visible = false; + } + } } } } diff --git a/src/AirspaceManagement/AirspaceFlightPlanProvider.cc b/src/AirspaceManagement/AirspaceFlightPlanProvider.cc index fde0a6ffe..729ec989a 100644 --- a/src/AirspaceManagement/AirspaceFlightPlanProvider.cc +++ b/src/AirspaceManagement/AirspaceFlightPlanProvider.cc @@ -7,12 +7,14 @@ * ****************************************************************************/ +#include "AirspaceManager.h" #include "AirspaceFlightPlanProvider.h" #include //----------------------------------------------------------------------------- AirspaceFlightInfo::AirspaceFlightInfo(QObject *parent) : QObject(parent) + , _beingDeleted(false) , _selected(false) { } @@ -40,6 +42,18 @@ AirspaceFlightModel::get(int index) return _flightEntries[index]; } +//----------------------------------------------------------------------------- +int +AirspaceFlightModel::findFlightPlanID(QString flightPlanID) +{ + for(int i = 0; i < _flightEntries.count(); i++) { + if(_flightEntries[i]->flightPlanID() == flightPlanID) { + return i; + } + } + return -1; +} + //----------------------------------------------------------------------------- int AirspaceFlightModel::count() const @@ -58,6 +72,30 @@ AirspaceFlightModel::append(AirspaceFlightInfo* object) emit countChanged(); } +//----------------------------------------------------------------------------- +void +AirspaceFlightModel::remove(const QString& flightPlanID) +{ + remove(findFlightPlanID(flightPlanID)); +} + +//----------------------------------------------------------------------------- +void +AirspaceFlightModel::remove(int index) +{ + if (index >= 0 && index < _flightEntries.count()) { + beginRemoveRows(QModelIndex(), index, index); + AirspaceFlightInfo* entry = _flightEntries[index]; + if(entry) { + qCDebug(AirspaceManagementLog) << "Deleting flight plan" << entry->flightPlanID(); + entry->deleteLater(); + } + _flightEntries.removeAt(index); + endRemoveRows(); + emit countChanged(); + } +} + //----------------------------------------------------------------------------- void AirspaceFlightModel::clear(void) diff --git a/src/AirspaceManagement/AirspaceFlightPlanProvider.h b/src/AirspaceManagement/AirspaceFlightPlanProvider.h index 017cba024..c3145229d 100644 --- a/src/AirspaceManagement/AirspaceFlightPlanProvider.h +++ b/src/AirspaceManagement/AirspaceFlightPlanProvider.h @@ -38,7 +38,8 @@ public: Q_PROPERTY(QString endTime READ endTime CONSTANT) Q_PROPERTY(QGeoCoordinate takeOff READ takeOff CONSTANT) Q_PROPERTY(QmlObjectListModel* boundingBox READ boundingBox CONSTANT) - Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged) + Q_PROPERTY(bool beingDeleted READ beingDeleted WRITE setBeingDeleted NOTIFY beingDeletedChanged) + Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged) virtual QString flightID () = 0; virtual QString flightPlanID () = 0; @@ -48,13 +49,17 @@ public: virtual QGeoCoordinate takeOff () = 0; virtual QmlObjectListModel* boundingBox () = 0; + virtual bool beingDeleted () { return _beingDeleted; } + virtual void setBeingDeleted (bool val) { _beingDeleted = val; emit beingDeletedChanged(); } virtual bool selected () { return _selected; } virtual void setSelected (bool sel) { _selected = sel; emit selectedChanged(); } signals: void selectedChanged (); + void beingDeletedChanged (); protected: + bool _beingDeleted; bool _selected; }; @@ -71,10 +76,14 @@ public: AirspaceFlightModel(QObject *parent = 0); Q_PROPERTY(int count READ count NOTIFY countChanged) - Q_INVOKABLE AirspaceFlightInfo* get(int index); + + Q_INVOKABLE AirspaceFlightInfo* get (int index); + Q_INVOKABLE int findFlightPlanID (QString flightPlanID); int count (void) const; void append (AirspaceFlightInfo *entry); + void remove (const QString& flightPlanID); + void remove (int index); void clear (void); AirspaceFlightInfo* @@ -124,11 +133,14 @@ public: Q_PROPERTY(QmlObjectListModel* rulesFollowing READ rulesFollowing NOTIFY rulesChanged) Q_PROPERTY(QmlObjectListModel* briefFeatures READ briefFeatures NOTIFY rulesChanged) Q_PROPERTY(AirspaceFlightModel* flightList READ flightList NOTIFY flightListChanged) + Q_PROPERTY(bool loadingFlightList READ loadingFlightList NOTIFY loadingFlightListChanged) //-- TODO: This will submit the current flight plan in memory. - Q_INVOKABLE virtual void submitFlightPlan () = 0; - Q_INVOKABLE virtual void updateFlightPlan () = 0; - Q_INVOKABLE virtual void loadFlightList () = 0; + Q_INVOKABLE virtual void submitFlightPlan () = 0; + Q_INVOKABLE virtual void updateFlightPlan () = 0; + Q_INVOKABLE virtual void loadFlightList (QDateTime startTime, QDateTime endTime) = 0; + Q_INVOKABLE virtual void deleteFlightPlan (QString flighPlanID) = 0; + Q_INVOKABLE virtual void deleteSelectedFlightPlans () = 0; virtual PermitStatus flightPermitStatus () const { return PermitNone; } virtual QDateTime flightStartTime () const = 0; @@ -145,6 +157,7 @@ public: virtual QmlObjectListModel* rulesFollowing () = 0; ///< List of AirspaceRule following virtual QmlObjectListModel* briefFeatures () = 0; ///< List of AirspaceRule in violation virtual AirspaceFlightModel*flightList () = 0; ///< List of AirspaceFlightInfo + virtual bool loadingFlightList () = 0; virtual void setFlightStartTime (QDateTime start) = 0; virtual void setFlightEndTime (QDateTime end) = 0; @@ -158,4 +171,5 @@ signals: void missionAreaChanged (); void rulesChanged (); void flightListChanged (); + void loadingFlightListChanged (); }; diff --git a/src/FactSystem/FactControls/FactTextField.qml b/src/FactSystem/FactControls/FactTextField.qml index 99c227d68..f17ea565c 100644 --- a/src/FactSystem/FactControls/FactTextField.qml +++ b/src/FactSystem/FactControls/FactTextField.qml @@ -32,7 +32,7 @@ QGCTextField { } if (typeof qgcView !== 'undefined' && qgcView) { var errorString = fact.validate(text, false /* convertOnly */) - if (errorString == "") { + if (errorString === "") { fact.value = text } else { _validateString = text -- 2.22.0