PlanMasterController.cc 24.3 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9 10 11
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

#include "PlanMasterController.h"
#include "QGCApplication.h"
12
#include "QGCCorePlugin.h"
13
#include "MultiVehicleManager.h"
14
#include "SettingsManager.h"
15 16
#include "AppSettings.h"
#include "JsonHelper.h"
17
#include "MissionManager.h"
18
#include "KMLPlanDomDocument.h"
DonLakeFlyer's avatar
DonLakeFlyer committed
19 20 21
#include "SurveyPlanCreator.h"
#include "StructureScanPlanCreator.h"
#include "CorridorScanPlanCreator.h"
22
#include "BlankPlanCreator.h"
23 24 25
#if defined(QGC_AIRMAP_ENABLED)
#include "AirspaceFlightPlanProvider.h"
#endif
26

27
#include <QDomDocument>
28
#include <QJsonDocument>
Don Gagne's avatar
Don Gagne committed
29
#include <QFileInfo>
30

DonLakeFlyer's avatar
DonLakeFlyer committed
31 32
QGC_LOGGING_CATEGORY(PlanMasterControllerLog, "PlanMasterControllerLog")

33 34 35 36 37
const int   PlanMasterController::kPlanFileVersion =            1;
const char* PlanMasterController::kPlanFileType =               "Plan";
const char* PlanMasterController::kJsonMissionObjectKey =       "mission";
const char* PlanMasterController::kJsonGeoFenceObjectKey =      "geoFence";
const char* PlanMasterController::kJsonRallyPointsObjectKey =   "rallyPoints";
38 39

PlanMasterController::PlanMasterController(QObject* parent)
40 41
    : QObject               (parent)
    , _multiVehicleMgr      (qgcApp()->toolbox()->multiVehicleManager())
42
    , _controllerVehicle    (new Vehicle(Vehicle::MAV_AUTOPILOT_TRACK, Vehicle::MAV_TYPE_TRACK, qgcApp()->toolbox()->firmwarePluginManager(), this))
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
    , _managerVehicle       (_controllerVehicle)
    , _missionController    (this)
    , _geoFenceController   (this)
    , _rallyPointController (this)
{
    _commonInit();
}

#ifdef QT_DEBUG
PlanMasterController::PlanMasterController(MAV_AUTOPILOT firmwareType, MAV_TYPE vehicleType, QObject* parent)
    : QObject               (parent)
    , _multiVehicleMgr      (qgcApp()->toolbox()->multiVehicleManager())
    , _controllerVehicle    (new Vehicle(firmwareType, vehicleType, qgcApp()->toolbox()->firmwarePluginManager()))
    , _managerVehicle       (_controllerVehicle)
    , _missionController    (this)
    , _geoFenceController   (this)
    , _rallyPointController (this)
{
    _commonInit();
}
#endif

void PlanMasterController::_commonInit(void)
66 67 68 69 70 71 72 73 74 75 76 77
{
    connect(&_missionController,    &MissionController::dirtyChanged,       this, &PlanMasterController::dirtyChanged);
    connect(&_geoFenceController,   &GeoFenceController::dirtyChanged,      this, &PlanMasterController::dirtyChanged);
    connect(&_rallyPointController, &RallyPointController::dirtyChanged,    this, &PlanMasterController::dirtyChanged);

    connect(&_missionController,    &MissionController::containsItemsChanged,       this, &PlanMasterController::containsItemsChanged);
    connect(&_geoFenceController,   &GeoFenceController::containsItemsChanged,      this, &PlanMasterController::containsItemsChanged);
    connect(&_rallyPointController, &RallyPointController::containsItemsChanged,    this, &PlanMasterController::containsItemsChanged);

    connect(&_missionController,    &MissionController::syncInProgressChanged,      this, &PlanMasterController::syncInProgressChanged);
    connect(&_geoFenceController,   &GeoFenceController::syncInProgressChanged,     this, &PlanMasterController::syncInProgressChanged);
    connect(&_rallyPointController, &RallyPointController::syncInProgressChanged,   this, &PlanMasterController::syncInProgressChanged);
78 79 80 81 82 83 84

    // Offline vehicle can change firmware/vehicle type
    connect(_controllerVehicle, &Vehicle::capabilityBitsChanged,    this, &PlanMasterController::_updateSupportsTerrain);
    connect(_controllerVehicle, &Vehicle::vehicleTypeChanged,       this, &PlanMasterController::_updateSupportsTerrain);
    connect(_controllerVehicle, &Vehicle::vehicleTypeChanged,       this, &PlanMasterController::_updatePlanCreatorsList);

    _updateSupportsTerrain();
85 86
}

87

88 89 90 91 92
PlanMasterController::~PlanMasterController()
{

}

93
void PlanMasterController::start(void)
94
{
95 96 97
    _missionController.start    (_flyView);
    _geoFenceController.start   (_flyView);
    _rallyPointController.start (_flyView);
98 99

    _activeVehicleChanged(_multiVehicleMgr->activeVehicle());
100
    connect(_multiVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &PlanMasterController::_activeVehicleChanged);
101

DonLakeFlyer's avatar
DonLakeFlyer committed
102 103
    _updatePlanCreatorsList();

104 105
#if defined(QGC_AIRMAP_ENABLED)
    //-- This assumes there is one single instance of PlanMasterController in edit mode.
106
    if(!flyView) {
107 108
        // Wait for signal confirming AirMap client connection before starting flight planning
        connect(qgcApp()->toolbox()->airspaceManager(), &AirspaceManager::connectStatusChanged, this, &PlanMasterController::_startFlightPlanning);
109 110
    }
#endif
111 112
}

113
void PlanMasterController::startStaticActiveVehicle(Vehicle* vehicle, bool deleteWhenSendCompleted)
114
{
115
    _flyView = true;
116
    _deleteWhenSendCompleted = deleteWhenSendCompleted;
117 118 119
    _missionController.start(_flyView);
    _geoFenceController.start(_flyView);
    _rallyPointController.start(_flyView);
120 121 122 123 124
    _activeVehicleChanged(vehicle);
}

void PlanMasterController::_activeVehicleChanged(Vehicle* activeVehicle)
{
125 126 127
    if (_managerVehicle == activeVehicle) {
        // We are already setup for this vehicle
        return;
128 129
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
130 131
    qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged" << activeVehicle;

132
    if (_managerVehicle) {
133 134 135 136 137
        // Disconnect old vehicle. Be careful of wildcarding disconnect too much since _managerVehicle may equal _controllerVehicle
        disconnect(_managerVehicle->missionManager(),       nullptr, nullptr, nullptr);
        disconnect(_managerVehicle->geoFenceManager(),      nullptr, nullptr, nullptr);
        disconnect(_managerVehicle->rallyPointManager(),    nullptr, nullptr, nullptr);
        disconnect(_managerVehicle,                         &Vehicle::capabilityBitsChanged, this, &PlanMasterController::_updateSupportsTerrain);
138 139
    }

140
    bool newOffline = false;
141
    if (activeVehicle == nullptr) {
142 143 144
        // Since there is no longer an active vehicle we use the offline controller vehicle as the manager vehicle
        _managerVehicle = _controllerVehicle;
        newOffline = true;
145
    } else {
146
        newOffline = false;
147 148 149 150 151 152 153 154
        _managerVehicle = activeVehicle;

        // Update controllerVehicle to the currently connected vehicle
        AppSettings* appSettings = qgcApp()->toolbox()->settingsManager()->appSettings();
        appSettings->offlineEditingFirmwareType()->setRawValue(AppSettings::offlineEditingFirmwareTypeFromFirmwareType(_managerVehicle->firmwareType()));
        appSettings->offlineEditingVehicleType()->setRawValue(AppSettings::offlineEditingVehicleTypeFromVehicleType(_managerVehicle->vehicleType()));

        // We use these signals to sequence upload and download to the multiple controller/managers
DonLakeFlyer's avatar
DonLakeFlyer committed
155 156 157 158 159 160
        connect(_managerVehicle->missionManager(),      &MissionManager::newMissionItemsAvailable,  this, &PlanMasterController::_loadMissionComplete);
        connect(_managerVehicle->geoFenceManager(),     &GeoFenceManager::loadComplete,             this, &PlanMasterController::_loadGeoFenceComplete);
        connect(_managerVehicle->rallyPointManager(),   &RallyPointManager::loadComplete,           this, &PlanMasterController::_loadRallyPointsComplete);
        connect(_managerVehicle->missionManager(),      &MissionManager::sendComplete,              this, &PlanMasterController::_sendMissionComplete);
        connect(_managerVehicle->geoFenceManager(),     &GeoFenceManager::sendComplete,             this, &PlanMasterController::_sendGeoFenceComplete);
        connect(_managerVehicle->rallyPointManager(),   &RallyPointManager::sendComplete,           this, &PlanMasterController::_sendRallyPointsComplete);
161
    }
162

163 164 165 166
    // Change in capabilities will affect terrain support
    connect(_managerVehicle, &Vehicle::capabilityBitsChanged, this, &PlanMasterController::_updateSupportsTerrain);

    emit managerVehicleChanged(_managerVehicle);
167

168 169 170 171 172 173
    // Vehicle changed so we need to signal everything
    _offline = newOffline;
    emit containsItemsChanged(containsItems());
    emit syncInProgressChanged();
    emit dirtyChanged(dirty());
    emit offlineChanged(offline());
174
    _updateSupportsTerrain();
175

176
    if (!_flyView) {
DonLakeFlyer's avatar
DonLakeFlyer committed
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
        if (!offline()) {
            // We are in Plan view and we have a newly connected vehicle:
            //  - If there is no plan available in Plan view show the one from the vehicle
            //  - Otherwise leave the current plan alone
            if (!containsItems()) {
                qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged: Plan view is empty so loading from manager";
                _showPlanFromManagerVehicle();
            }
        }
    } else {
        if (offline()) {
            // No more active vehicle, clear mission
            qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged: Fly view is offline clearing plan";
            removeAll();
        } else {
            // Fly view has changed to a new active vehicle, update to show correct mission
            qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged: Fly view is online so loading from manager";
            _showPlanFromManagerVehicle();
        }
196
    }
DonLakeFlyer's avatar
DonLakeFlyer committed
197 198

    _updatePlanCreatorsList();
199 200 201 202
}

void PlanMasterController::loadFromVehicle(void)
{
203
    if (_managerVehicle->highLatencyLink()) {
204
        qgcApp()->showAppMessage(tr("Download not supported on high latency links."));
205 206 207
        return;
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
208 209
    if (offline()) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called while offline";
210
    } else if (_flyView) {
DonLakeFlyer's avatar
DonLakeFlyer committed
211 212 213 214
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called from Fly view";
    } else if (syncInProgress()) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called while syncInProgress";
    } else {
215
        _loadGeoFence = true;
216
        qCDebug(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle calling _missionController.loadFromVehicle";
217 218 219
        _missionController.loadFromVehicle();
        setDirty(false);
    }
220 221
}

222

DonLakeFlyer's avatar
DonLakeFlyer committed
223
void PlanMasterController::_loadMissionComplete(void)
224
{
225
    if (!_flyView && _loadGeoFence) {
226 227
        _loadGeoFence = false;
        _loadRallyPoints = true;
228
        if (_geoFenceController.supported()) {
229
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete calling _geoFenceController.loadFromVehicle";
230 231 232 233 234 235
            _geoFenceController.loadFromVehicle();
        } else {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete GeoFence not supported skipping";
            _geoFenceController.removeAll();
            _loadGeoFenceComplete();
        }
236
        setDirty(false);
DonLakeFlyer's avatar
DonLakeFlyer committed
237 238 239 240 241
    }
}

void PlanMasterController::_loadGeoFenceComplete(void)
{
242
    if (!_flyView && _loadRallyPoints) {
DonLakeFlyer's avatar
DonLakeFlyer committed
243
        _loadRallyPoints = false;
244
        if (_rallyPointController.supported()) {
245
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadGeoFenceComplete calling _rallyPointController.loadFromVehicle";
246 247 248 249 250 251
            _rallyPointController.loadFromVehicle();
        } else {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete Rally Points not supported skipping";
            _rallyPointController.removeAll();
            _loadRallyPointsComplete();
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
252 253 254 255 256 257
        setDirty(false);
    }
}

void PlanMasterController::_loadRallyPointsComplete(void)
{
258
    qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadRallyPointsComplete";
DonLakeFlyer's avatar
DonLakeFlyer committed
259 260 261 262
}

void PlanMasterController::_sendMissionComplete(void)
{
263
    if (_sendGeoFence) {
264 265
        _sendGeoFence = false;
        _sendRallyPoints = true;
266 267 268 269 270 271 272
        if (_geoFenceController.supported()) {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle start GeoFence sendToVehicle";
            _geoFenceController.sendToVehicle();
        } else {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle GeoFence not supported skipping";
            _sendGeoFenceComplete();
        }
273 274 275 276
        setDirty(false);
    }
}

DonLakeFlyer's avatar
DonLakeFlyer committed
277
void PlanMasterController::_sendGeoFenceComplete(void)
278
{
279
    if (_sendRallyPoints) {
280
        _sendRallyPoints = false;
281 282 283 284 285 286 287
        if (_rallyPointController.supported()) {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle start rally sendToVehicle";
            _rallyPointController.sendToVehicle();
        } else {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle Rally Points not support skipping";
            _sendRallyPointsComplete();
        }
288 289 290
    }
}

DonLakeFlyer's avatar
DonLakeFlyer committed
291 292
void PlanMasterController::_sendRallyPointsComplete(void)
{
293
    qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle Rally Point send complete";
294 295 296
    if (_deleteWhenSendCompleted) {
        this->deleteLater();
    }
DonLakeFlyer's avatar
DonLakeFlyer committed
297 298
}

299
#if defined(QGC_AIRMAP_ENABLED)
300
void PlanMasterController::_startFlightPlanning(void) {
301 302 303 304
    if (qgcApp()->toolbox()->airspaceManager()->connected()) {
        qCDebug(PlanMasterControllerLog) << "PlanMasterController::_startFlightPlanning client connected, start flight planning";
        qgcApp()->toolbox()->airspaceManager()->flightPlan()->startFlightPlanning(this);
    }
305
}
306
#endif
307

308 309
void PlanMasterController::sendToVehicle(void)
{
310
    if (_managerVehicle->highLatencyLink()) {
311
        qgcApp()->showAppMessage(tr("Upload not supported on high latency links."));
312 313 314
        return;
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
315 316 317 318 319 320
    if (offline()) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle called while offline";
    } else if (syncInProgress()) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle called while syncInProgress";
    } else {
        qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle start mission sendToVehicle";
321 322 323 324
        _sendGeoFence = true;
        _missionController.sendToVehicle();
        setDirty(false);
    }
325 326 327 328 329
}

void PlanMasterController::loadFromFile(const QString& filename)
{
    QString errorString;
330
    QString errorMessage = tr("Error loading Plan file (%1). %2").arg(filename).arg("%1");
331 332 333 334 335

    if (filename.isEmpty()) {
        return;
    }

336
    QFileInfo fileInfo(filename);
337 338 339 340
    QFile file(filename);

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        errorString = file.errorString() + QStringLiteral(" ") + filename;
341
        qgcApp()->showAppMessage(errorMessage.arg(errorString));
342 343 344
        return;
    }

345 346
    bool success = false;
    if(fileInfo.suffix() == AppSettings::planFileExtension) {
347 348 349 350
        QJsonDocument   jsonDoc;
        QByteArray      bytes = file.readAll();

        if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorString)) {
351
            qgcApp()->showAppMessage(errorMessage.arg(errorString));
352 353 354 355
            return;
        }

        QJsonObject json = jsonDoc.object();
356 357 358 359
        //-- Allow plugins to pre process the load
        qgcApp()->toolbox()->corePlugin()->preLoadFromJson(this, json);

        int version;
360
        if (!JsonHelper::validateExternalQGCJsonFile(json, kPlanFileType, kPlanFileVersion, kPlanFileVersion, version, errorString)) {
361
            qgcApp()->showAppMessage(errorMessage.arg(errorString));
362 363 364 365
            return;
        }

        QList<JsonHelper::KeyValidateInfo> rgKeyInfo = {
366 367 368
            { kJsonMissionObjectKey,        QJsonValue::Object, true },
            { kJsonGeoFenceObjectKey,       QJsonValue::Object, true },
            { kJsonRallyPointsObjectKey,    QJsonValue::Object, true },
369 370
        };
        if (!JsonHelper::validateKeys(json, rgKeyInfo, errorString)) {
371
            qgcApp()->showAppMessage(errorMessage.arg(errorString));
372 373 374
            return;
        }

375 376 377
        if (!_missionController.load(json[kJsonMissionObjectKey].toObject(), errorString) ||
                !_geoFenceController.load(json[kJsonGeoFenceObjectKey].toObject(), errorString) ||
                !_rallyPointController.load(json[kJsonRallyPointsObjectKey].toObject(), errorString)) {
378
            qgcApp()->showAppMessage(errorMessage.arg(errorString));
379
        } else {
380 381
            //-- Allow plugins to post process the load
            qgcApp()->toolbox()->corePlugin()->postLoadFromJson(this, json);
382
            success = true;
383
        }
384
    } else if (fileInfo.suffix() == AppSettings::missionFileExtension) {
385
        if (!_missionController.loadJsonFile(file, errorString)) {
386
            qgcApp()->showAppMessage(errorMessage.arg(errorString));
387 388
        } else {
            success = true;
389
        }
390
    } else if (fileInfo.suffix() == AppSettings::waypointsFileExtension || fileInfo.suffix() == QStringLiteral("txt")) {
391
        if (!_missionController.loadTextFile(file, errorString)) {
392
            qgcApp()->showAppMessage(errorMessage.arg(errorString));
393 394
        } else {
            success = true;
395
        }
396 397 398 399 400
    } else {
        //-- TODO: What then?
    }

    if(success){
401
        _currentPlanFile = QString::asprintf("%s/%s.%s", fileInfo.path().toLocal8Bit().data(), fileInfo.completeBaseName().toLocal8Bit().data(), AppSettings::planFileExtension);
402 403
    } else {
        _currentPlanFile.clear();
404
    }
405
    emit currentPlanFileChanged();
406

407
    if (!offline()) {
408
        setDirty(true);
409 410 411
    }
}

Gus Grubba's avatar
Gus Grubba committed
412 413 414
QJsonDocument PlanMasterController::saveToJson()
{
    QJsonObject planJson;
415
    qgcApp()->toolbox()->corePlugin()->preSaveToJson(this, planJson);
Gus Grubba's avatar
Gus Grubba committed
416 417 418
    QJsonObject missionJson;
    QJsonObject fenceJson;
    QJsonObject rallyJson;
419 420 421
    JsonHelper::saveQGCJsonFileHeader(planJson, kPlanFileType, kPlanFileVersion);
    //-- Allow plugin to preemptly add its own keys to mission
    qgcApp()->toolbox()->corePlugin()->preSaveToMissionJson(this, missionJson);
Gus Grubba's avatar
Gus Grubba committed
422
    _missionController.save(missionJson);
423 424
    //-- Allow plugin to add its own keys to mission
    qgcApp()->toolbox()->corePlugin()->postSaveToMissionJson(this, missionJson);
Gus Grubba's avatar
Gus Grubba committed
425 426
    _geoFenceController.save(fenceJson);
    _rallyPointController.save(rallyJson);
427 428 429 430
    planJson[kJsonMissionObjectKey] = missionJson;
    planJson[kJsonGeoFenceObjectKey] = fenceJson;
    planJson[kJsonRallyPointsObjectKey] = rallyJson;
    qgcApp()->toolbox()->corePlugin()->postSaveToJson(this, planJson);
Gus Grubba's avatar
Gus Grubba committed
431 432 433
    return QJsonDocument(planJson);
}

434 435 436 437 438 439 440 441
void
PlanMasterController::saveToCurrent()
{
    if(!_currentPlanFile.isEmpty()) {
        saveToFile(_currentPlanFile);
    }
}

442 443 444 445 446 447 448 449 450 451 452 453 454 455
void PlanMasterController::saveToFile(const QString& filename)
{
    if (filename.isEmpty()) {
        return;
    }

    QString planFilename = filename;
    if (!QFileInfo(filename).fileName().contains(".")) {
        planFilename += QString(".%1").arg(fileExtension());
    }

    QFile file(planFilename);

    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
456
        qgcApp()->showAppMessage(tr("Plan save error %1 : %2").arg(filename).arg(file.errorString()));
457 458
        _currentPlanFile.clear();
        emit currentPlanFileChanged();
459
    } else {
Gus Grubba's avatar
Gus Grubba committed
460
        QJsonDocument saveDoc = saveToJson();
461
        file.write(saveDoc.toJson());
462 463 464 465
        if(_currentPlanFile != planFilename) {
            _currentPlanFile = planFilename;
            emit currentPlanFileChanged();
        }
466 467
    }

468 469
    // Only clear dirty bit if we are offline
    if (offline()) {
470 471 472 473
        setDirty(false);
    }
}

474 475 476 477 478 479 480 481 482 483 484 485 486 487
void PlanMasterController::saveToKml(const QString& filename)
{
    if (filename.isEmpty()) {
        return;
    }

    QString kmlFilename = filename;
    if (!QFileInfo(filename).fileName().contains(".")) {
        kmlFilename += QString(".%1").arg(kmlFileExtension());
    }

    QFile file(kmlFilename);

    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
488
        qgcApp()->showAppMessage(tr("KML save error %1 : %2").arg(filename).arg(file.errorString()));
489
    } else {
490 491
        KMLPlanDomDocument planKML;
        _missionController.addMissionToKML(planKML);
492
        QTextStream stream(&file);
493
        stream << planKML.toString();
494 495 496 497
        file.close();
    }
}

498 499
void PlanMasterController::removeAll(void)
{
500 501 502
    _missionController.removeAll();
    _geoFenceController.removeAll();
    _rallyPointController.removeAll();
503 504 505 506
    if (_offline) {
        _missionController.setDirty(false);
        _geoFenceController.setDirty(false);
        _rallyPointController.setDirty(false);
507 508
        _currentPlanFile.clear();
        emit currentPlanFileChanged();
509
    }
510 511 512 513
}

void PlanMasterController::removeAllFromVehicle(void)
{
514 515
    if (!offline()) {
        _missionController.removeAllFromVehicle();
516 517 518 519 520 521
        if (_geoFenceController.supported()) {
            _geoFenceController.removeAllFromVehicle();
        }
        if (_rallyPointController.supported()) {
            _rallyPointController.removeAllFromVehicle();
        }
522
        setDirty(false);
523 524 525
    } else {
        qWarning() << "PlanMasterController::removeAllFromVehicle called while offline";
    }
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
}

bool PlanMasterController::containsItems(void) const
{
    return _missionController.containsItems() || _geoFenceController.containsItems() || _rallyPointController.containsItems();
}

bool PlanMasterController::dirty(void) const
{
    return _missionController.dirty() || _geoFenceController.dirty() || _rallyPointController.dirty();
}

void PlanMasterController::setDirty(bool dirty)
{
    _missionController.setDirty(dirty);
    _geoFenceController.setDirty(dirty);
    _rallyPointController.setDirty(dirty);
}

QString PlanMasterController::fileExtension(void) const
{
    return AppSettings::planFileExtension;
}

550 551 552 553 554
QString PlanMasterController::kmlFileExtension(void) const
{
    return AppSettings::kmlFileExtension;
}

555 556 557 558
QStringList PlanMasterController::loadNameFilters(void) const
{
    QStringList filters;

Don Gagne's avatar
Don Gagne committed
559
    filters << tr("Supported types (*.%1 *.%2 *.%3 *.%4)").arg(AppSettings::planFileExtension).arg(AppSettings::missionFileExtension).arg(AppSettings::waypointsFileExtension).arg("txt") <<
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
               tr("All Files (*.*)");
    return filters;
}


QStringList PlanMasterController::saveNameFilters(void) const
{
    QStringList filters;

    filters << tr("Plan Files (*.%1)").arg(fileExtension()) << tr("All Files (*.*)");
    return filters;
}

void PlanMasterController::sendPlanToVehicle(Vehicle* vehicle, const QString& filename)
{
    // Use a transient PlanMasterController to accomplish this
    PlanMasterController* controller = new PlanMasterController();
577
    controller->startStaticActiveVehicle(vehicle, true /* deleteWhenSendCompleted */);
578
    controller->loadFromFile(filename);
579
    controller->sendToVehicle();
580
}
DonLakeFlyer's avatar
DonLakeFlyer committed
581 582 583

void PlanMasterController::_showPlanFromManagerVehicle(void)
{
584
    if (!_managerVehicle->initialPlanRequestComplete() && !syncInProgress()) {
585 586 587 588
        // Something went wrong with initial load. All controllers are idle, so just force it off
        _managerVehicle->forceInitialPlanRequestComplete();
    }

589
    // The crazy if structure is to handle the load propagating by itself through the system
DonLakeFlyer's avatar
DonLakeFlyer committed
590 591 592 593 594 595
    if (!_missionController.showPlanFromManagerVehicle()) {
        if (!_geoFenceController.showPlanFromManagerVehicle()) {
            _rallyPointController.showPlanFromManagerVehicle();
        }
    }
}
596 597 598 599 600 601 602

bool PlanMasterController::syncInProgress(void) const
{
    return _missionController.syncInProgress() ||
            _geoFenceController.syncInProgress() ||
            _rallyPointController.syncInProgress();
}
603 604 605 606 607 608 609

bool PlanMasterController::isEmpty(void) const
{
    return _missionController.isEmpty() &&
            _geoFenceController.isEmpty() &&
            _rallyPointController.isEmpty();
}
DonLakeFlyer's avatar
DonLakeFlyer committed
610 611 612 613 614 615

void PlanMasterController::_updatePlanCreatorsList(void)
{
    if (!_flyView) {
        if (!_planCreators) {
            _planCreators = new QmlObjectListModel(this);
616
            _planCreators->append(new BlankPlanCreator(this, this));
DonLakeFlyer's avatar
DonLakeFlyer committed
617 618 619 620 621 622 623
            _planCreators->append(new SurveyPlanCreator(this, this));
            _planCreators->append(new CorridorScanPlanCreator(this, this));
            emit planCreatorsChanged(_planCreators);
        }

        if (_managerVehicle->fixedWing()) {
            if (_planCreators->count() == 4) {
624
                _planCreators->removeAt(_planCreators->count() - 1);
DonLakeFlyer's avatar
DonLakeFlyer committed
625 626 627
            }
        } else {
            if (_planCreators->count() != 4) {
628
                _planCreators->append(new StructureScanPlanCreator(this, this));
DonLakeFlyer's avatar
DonLakeFlyer committed
629 630 631 632
            }
        }
    }
}
633 634 635 636 637 638 639 640 641

void PlanMasterController::_updateSupportsTerrain(void)
{
    bool supportsTerrain = _managerVehicle->capabilityBits() & MAV_PROTOCOL_CAPABILITY_TERRAIN;
    if (supportsTerrain != _supportsTerrain) {
        _supportsTerrain = supportsTerrain;
        emit supportsTerrainChanged(supportsTerrain);
    }
}