text: /****************************************************************************
   *
   * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
   *
   * 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"
  #include "QGCCorePlugin.h"
  #include "MultiVehicleManager.h"
  #include "SettingsManager.h"
  #include "AppSettings.h"
  #include "JsonHelper.h"
  #include "MissionManager.h"
  #include "KMLPlanDomDocument.h"
  #include "SurveyPlanCreator.h"
  #include "StructureScanPlanCreator.h"
  #include "CorridorScanPlanCreator.h"
  #include "BlankPlanCreator.h"
  #if defined(QGC_AIRMAP_ENABLED)
  #include "AirspaceFlightPlanProvider.h"
  #endif
  
  #include <QDomDocument>
  #include <QJsonDocument>
  #include <QFileInfo>
  
  QGC_LOGGING_CATEGORY(PlanMasterControllerLog, "PlanMasterControllerLog")
  
  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";
  
  PlanMasterController::PlanMasterController(QObject* parent)
      : QObject               (parent)
      , _multiVehicleMgr      (qgcApp()->toolbox()->multiVehicleManager())
      , _controllerVehicle    (new Vehicle(Vehicle::MAV_AUTOPILOT_TRACK, Vehicle::MAV_TYPE_TRACK, qgcApp()->toolbox()->firmwarePluginManager(), this))
      , _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)
  {
      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);
  
      // 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();
  }
  
  
  PlanMasterController::~PlanMasterController()
  {
  
  }
  
  void PlanMasterController::start(void)
  {
      _missionController.start    (_flyView);
      _geoFenceController.start   (_flyView);
      _rallyPointController.start (_flyView);
  
      _activeVehicleChanged(_multiVehicleMgr->activeVehicle());
      connect(_multiVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &PlanMasterController::_activeVehicleChanged);
  
      _updatePlanCreatorsList();
  
  #if defined(QGC_AIRMAP_ENABLED)
      //-- This assumes there is one single instance of PlanMasterController in edit mode.
      if(!flyView) {
          // Wait for signal confirming AirMap client connection before starting flight planning
          connect(qgcApp()->toolbox()->airspaceManager(), &AirspaceManager::connectStatusChanged, this, &PlanMasterController::_startFlightPlanning);
      }
  #endif
  }
  
  void PlanMasterController::startStaticActiveVehicle(Vehicle* vehicle, bool deleteWhenSendCompleted)
  {
      _flyView = true;
      _deleteWhenSendCompleted = deleteWhenSendCompleted;
      _missionController.start(_flyView);
      _geoFenceController.start(_flyView);
      _rallyPointController.start(_flyView);
      _activeVehicleChanged(vehicle);
  }
  
  void PlanMasterController::_activeVehicleChanged(Vehicle* activeVehicle)
  {
      if (_managerVehicle == activeVehicle) {
          // We are already setup for this vehicle
          return;
      }
  
      qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged" << activeVehicle;
  
      if (_managerVehicle) {
          // 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);
      }
  
      bool newOffline = false;
      if (activeVehicle == nullptr) {
          // Since there is no longer an active vehicle we use the offline controller vehicle as the manager vehicle
          _managerVehicle = _controllerVehicle;
          newOffline = true;
      } else {
          newOffline = false;
          _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
          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);
      }
  
      // Change in capabilities will affect terrain support
      connect(_managerVehicle, &Vehicle::capabilityBitsChanged, this, &PlanMasterController::_updateSupportsTerrain);
  
      emit managerVehicleChanged(_managerVehicle);
  
      // Vehicle changed so we need to signal everything
      _offline = newOffline;
      emit containsItemsChanged(containsItems());
      emit syncInProgressChanged();
      emit dirtyChanged(dirty());
      emit offlineChanged(offline());
      _updateSupportsTerrain();
  
      if (!_flyView) {
          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();
          }
      }
  
      _updatePlanCreatorsList();
  }
  
  void PlanMasterController::loadFromVehicle(void)
  {
      if (_managerVehicle->highLatencyLink()) {
          qgcApp()->showAppMessage(tr("Download not supported on high latency links."));
          return;
      }
  
      if (offline()) {
          qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called while offline";
      } else if (_flyView) {
          qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called from Fly view";
      } else if (syncInProgress()) {
          qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called while syncInProgress";
      } else {
          _loadGeoFence = true;
          qCDebug(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle calling _missionController.loadFromVehicle";
          _missionController.loadFromVehicle();
          setDirty(false);
      }
  }
  
  
  void PlanMasterController::_loadMissionComplete(void)
  {
      if (!_flyView && _loadGeoFence) {
          _loadGeoFence = false;
          _loadRallyPoints = true;
          if (_geoFenceController.supported()) {
              qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete calling _geoFenceController.loadFromVehicle";
              _geoFenceController.loadFromVehicle();
          } else {
              qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete GeoFence not supported skipping";
              _geoFenceController.removeAll();
              _loadGeoFenceComplete();
          }
          setDirty(false);
      }
  }
  
  void PlanMasterController::_loadGeoFenceComplete(void)
  {
      if (!_flyView && _loadRallyPoints) {
          _loadRallyPoints = false;
          if (_rallyPointController.supported()) {
              qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadGeoFenceComplete calling _rallyPointController.loadFromVehicle";
              _rallyPointController.loadFromVehicle();
          } else {
              qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete Rally Points not supported skipping";
              _rallyPointController.removeAll();
              _loadRallyPointsComplete();
          }
          setDirty(false);
      }
  }
  
  void PlanMasterController::_loadRallyPointsComplete(void)
  {
      qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadRallyPointsComplete";
  }
  
  void PlanMasterController::_sendMissionComplete(void)
  {
      if (_sendGeoFence) {
          _sendGeoFence = false;
          _sendRallyPoints = true;
          if (_geoFenceController.supported()) {
              qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle start GeoFence sendToVehicle";
              _geoFenceController.sendToVehicle();
          } else {
              qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle GeoFence not supported skipping";
              _sendGeoFenceComplete();
          }
          setDirty(false);
      }
  }
  
  void PlanMasterController::_sendGeoFenceComplete(void)
  {
      if (_sendRallyPoints) {
          _sendRallyPoints = false;
          if (_rallyPointController.supported()) {
              qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle start rally sendToVehicle";
              _rallyPointController.sendToVehicle();
          } else {
              qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle Rally Points not support skipping";
              _sendRallyPointsComplete();
          }
      }
  }
  
  void PlanMasterController::_sendRallyPointsComplete(void)
  {
      qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle Rally Point send complete";
      if (_deleteWhenSendCompleted) {
          this->deleteLater();
      }
  }
  
  #if defined(QGC_AIRMAP_ENABLED)
  void PlanMasterController::_startFlightPlanning(void) {
      if (qgcApp()->toolbox()->airspaceManager()->connected()) {
          qCDebug(PlanMasterControllerLog) << "PlanMasterController::_startFlightPlanning client connected, start flight planning";
          qgcApp()->toolbox()->airspaceManager()->flightPlan()->startFlightPlanning(this);
      }
  }
  #endif
  
  void PlanMasterController::sendToVehicle(void)
  {
      if (_managerVehicle->highLatencyLink()) {
          qgcApp()->showAppMessage(tr("Upload not supported on high latency links."));
          return;
      }
  
      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";
          _sendGeoFence = true;
          _missionController.sendToVehicle();
          setDirty(false);
      }
  }
  
  void PlanMasterController::loadFromFile(const QString& filename)
  {
      QString errorString;
      QString errorMessage = tr("Error loading Plan file (%1). %2").arg(filename).arg("%1");
  
      if (filename.isEmpty()) {
          return;
      }
  
      QFileInfo fileInfo(filename);
      QFile file(filename);
  
      if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
          errorString = file.errorString() + QStringLiteral(" ") + filename;
          qgcApp()->showAppMessage(errorMessage.arg(errorString));
          return;
      }
  
      bool success = false;
      if(fileInfo.suffix() == AppSettings::planFileExtension) {
          QJsonDocument   jsonDoc;
          QByteArray      bytes = file.readAll();
  
          if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorString)) {
              qgcApp()->showAppMessage(errorMessage.arg(errorString));
              return;
          }
  
          QJsonObject json = jsonDoc.object();
          //-- Allow plugins to pre process the load
          qgcApp()->toolbox()->corePlugin()->preLoadFromJson(this, json);
  
          int version;
          if (!JsonHelper::validateExternalQGCJsonFile(json, kPlanFileType, kPlanFileVersion, kPlanFileVersion, version, errorString)) {
              qgcApp()->showAppMessage(errorMessage.arg(errorString));
              return;
          }
  
          QList<JsonHelper::KeyValidateInfo> rgKeyInfo = {
              { kJsonMissionObjectKey,        QJsonValue::Object, true },
              { kJsonGeoFenceObjectKey,       QJsonValue::Object, true },
              { kJsonRallyPointsObjectKey,    QJsonValue::Object, true },
          };
          if (!JsonHelper::validateKeys(json, rgKeyInfo, errorString)) {
              qgcApp()->showAppMessage(errorMessage.arg(errorString));
              return;
          }
  
          if (!_missionController.load(json[kJsonMissionObjectKey].toObject(), errorString) ||
                  !_geoFenceController.load(json[kJsonGeoFenceObjectKey].toObject(), errorString) ||
                  !_rallyPointController.load(json[kJsonRallyPointsObjectKey].toObject(), errorString)) {
              qgcApp()->showAppMessage(errorMessage.arg(errorString));
          } else {
              //-- Allow plugins to post process the load
              qgcApp()->toolbox()->corePlugin()->postLoadFromJson(this, json);
              success = true;
          }
      } else if (fileInfo.suffix() == AppSettings::missionFileExtension) {
          if (!_missionController.loadJsonFile(file, errorString)) {
              qgcApp()->showAppMessage(errorMessage.arg(errorString));
          } else {
              success = true;
          }
      } else if (fileInfo.suffix() == AppSettings::waypointsFileExtension || fileInfo.suffix() == QStringLiteral("txt")) {
          if (!_missionController.loadTextFile(file, errorString)) {
              qgcApp()->showAppMessage(errorMessage.arg(errorString));
          } else {
              success = true;
          }
      } else {
          //-- TODO: What then?
      }
  
      if(success){
          _currentPlanFile = QString::asprintf("%s/%s.%s", fileInfo.path().toLocal8Bit().data(), fileInfo.completeBaseName().toLocal8Bit().data(), AppSettings::planFileExtension);
      } else {
          _currentPlanFile.clear();
      }
      emit currentPlanFileChanged();
  
      if (!offline()) {
          setDirty(true);
      }
  }
  
  QJsonDocument PlanMasterController::saveToJson()
  {
      QJsonObject planJson;
      qgcApp()->toolbox()->corePlugin()->preSaveToJson(this, planJson);
      QJsonObject missionJson;
      QJsonObject fenceJson;
      QJsonObject rallyJson;
      JsonHelper::saveQGCJsonFileHeader(planJson, kPlanFileType, kPlanFileVersion);
      //-- Allow plugin to preemptly add its own keys to mission
      qgcApp()->toolbox()->corePlugin()->preSaveToMissionJson(this, missionJson);
      _missionController.save(missionJson);
      //-- Allow plugin to add its own keys to mission
      qgcApp()->toolbox()->corePlugin()->postSaveToMissionJson(this, missionJson);
      _geoFenceController.save(fenceJson);
      _rallyPointController.save(rallyJson);
      planJson[kJsonMissionObjectKey] = missionJson;
      planJson[kJsonGeoFenceObjectKey] = fenceJson;
      planJson[kJsonRallyPointsObjectKey] = rallyJson;
      qgcApp()->toolbox()->corePlugin()->postSaveToJson(this, planJson);
      return QJsonDocument(planJson);
  }
  
  void
  PlanMasterController::saveToCurrent()
  {
      if(!_currentPlanFile.isEmpty()) {
          saveToFile(_currentPlanFile);
      }
  }
  
  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)) {
          qgcApp()->showAppMessage(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
      if (offline()) {
          setDirty(false);
      }
  }
  
  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)) {
          qgcApp()->showAppMessage(tr("KML save error %1 : %2").arg(filename).arg(file.errorString()));
      } else {
          KMLPlanDomDocument planKML;
          _missionController.addMissionToKML(planKML);
          QTextStream stream(&file);
          stream << planKML.toString();
          file.close();
      }
  }
  
  void PlanMasterController::removeAll(void)
  {
      _missionController.removeAll();
      _geoFenceController.removeAll();
      _rallyPointController.removeAll();
      if (_offline) {
          _missionController.setDirty(false);
          _geoFenceController.setDirty(false);
          _rallyPointController.setDirty(false);
          _currentPlanFile.clear();
          emit currentPlanFileChanged();
      }
  }
  
  void PlanMasterController::removeAllFromVehicle(void)
  {
      if (!offline()) {
          _missionController.removeAllFromVehicle();
          if (_geoFenceController.supported()) {
              _geoFenceController.removeAllFromVehicle();
          }
          if (_rallyPointController.supported()) {
              _rallyPointController.removeAllFromVehicle();
          }
          setDirty(false);
      } else {
          qWarning() << "PlanMasterController::removeAllFromVehicle called while offline";
      }
  }
  
  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;
  }
  
  QString PlanMasterController::kmlFileExtension(void) const
  {
      return AppSettings::kmlFileExtension;
  }
  
  QStringList PlanMasterController::loadNameFilters(void) const
  {
      QStringList filters;
  
      filters << tr("Supported types (*.%1 *.%2 *.%3 *.%4)").arg(AppSettings::planFileExtension).arg(AppSettings::missionFileExtension).arg(AppSettings::waypointsFileExtension).arg("txt") <<
                 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();
      controller->startStaticActiveVehicle(vehicle, true /* deleteWhenSendCompleted */);
      controller->loadFromFile(filename);
      controller->sendToVehicle();
  }
  
  void PlanMasterController::_showPlanFromManagerVehicle(void)
  {
      if (!_managerVehicle->initialPlanRequestComplete() && !syncInProgress()) {
          // Something went wrong with initial load. All controllers are idle, so just force it off
          _managerVehicle->forceInitialPlanRequestComplete();
      }
  
      // The crazy if structure is to handle the load propagating by itself through the system
      if (!_missionController.showPlanFromManagerVehicle()) {
          if (!_geoFenceController.showPlanFromManagerVehicle()) {
              _rallyPointController.showPlanFromManagerVehicle();
          }
      }
  }
  
  bool PlanMasterController::syncInProgress(void) const
  {
      return _missionController.syncInProgress() ||
              _geoFenceController.syncInProgress() ||
              _rallyPointController.syncInProgress();
  }
  
  bool PlanMasterController::isEmpty(void) const
  {
      return _missionController.isEmpty() &&
              _geoFenceController.isEmpty() &&
              _rallyPointController.isEmpty();
  }
  
  void PlanMasterController::_updatePlanCreatorsList(void)
  {
      if (!_flyView) {
          if (!_planCreators) {
              _planCreators = new QmlObjectListModel(this);
              _planCreators->append(new BlankPlanCreator(this, this));
              _planCreators->append(new SurveyPlanCreator(this, this));
              _planCreators->append(new CorridorScanPlanCreator(this, this));
              emit planCreatorsChanged(_planCreators);
          }
  
          if (_managerVehicle->fixedWing()) {
              if (_planCreators->count() == 4) {
                  _planCreators->removeAt(_planCreators->count() - 1);
              }
          } else {
              if (_planCreators->count() != 4) {
                  _planCreators->append(new StructureScanPlanCreator(this, this));
              }
          }
      }
  }
  
  void PlanMasterController::_updateSupportsTerrain(void)
  {
      bool supportsTerrain = _managerVehicle->capabilityBits() & MAV_PROTOCOL_CAPABILITY_TERRAIN;
      if (supportsTerrain != _supportsTerrain) {
          _supportsTerrain = supportsTerrain;
          emit supportsTerrainChanged(supportsTerrain);
      }
  }
  