| Estimated | %1").arg(etd));
                }
                if (!ata.isEmpty()) {
                    flightDetails.append(QString(" | Actual | %1").arg(ata));
                } else if (!eta.isEmpty()) {
                    flightDetails.append(QString(" | Estimated | %1").arg(eta));
                }
            }
            flightDetails.append("");
        }
        ui->flightDetails->setText(flightDetails);
    }
}
void ADSBDemodGUI::highlightAircraft(Aircraft *aircraft)
{
    if (aircraft != m_highlightAircraft)
    {
        // Hide photo of old aircraft
        ui->photoHeader->setVisible(false);
        ui->photoFlag->setVisible(false);
        ui->photo->setVisible(false);
        ui->flightDetails->setVisible(false);
        ui->aircraftDetails->setVisible(false);
        if (m_highlightAircraft)
        {
            // Restore colour
            m_highlightAircraft->m_isHighlighted = false;
            m_aircraftModel.aircraftUpdated(m_highlightAircraft);
        }
        // Highlight this aircraft
        m_highlightAircraft = aircraft;
        if (aircraft)
        {
            aircraft->m_isHighlighted = true;
            m_aircraftModel.aircraftUpdated(aircraft);
            if (m_settings.m_displayPhotos)
            {
                // Download photo
                updatePhotoText(aircraft);
                m_planeSpotters.getAircraftPhoto(aircraft->m_icaoHex);
            }
        }
    }
    if (aircraft)
    {
        // Highlight the row in the table - always do this, as it can become
        // unselected
        ui->adsbData->selectRow(aircraft->m_icaoItem->row());
    }
    else
    {
        ui->adsbData->clearSelection();
    }
}
// Show feed dialog
void ADSBDemodGUI::feedSelect(const QPoint& p)
{
    ADSBDemodFeedDialog dialog(&m_settings);
    dialog.move(p);
    if (dialog.exec() == QDialog::Accepted)
    {
        applySettings();
        applyImportSettings();
    }
}
// Show display settings dialog
void ADSBDemodGUI::on_displaySettings_clicked()
{
    bool oldSiUnits = m_settings.m_siUnits;
    ADSBDemodDisplayDialog dialog(&m_settings);
    if (dialog.exec() == QDialog::Accepted)
    {
        bool unitsChanged = m_settings.m_siUnits != oldSiUnits;
        if (unitsChanged) {
            m_aircraftModel.allAircraftUpdated();
        }
        displaySettings();
        applySettings();
    }
}
void ADSBDemodGUI::applyMapSettings()
{
    Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
    Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
    Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
    QQuickItem *item = ui->map->rootObject();
    if (!item)
    {
        qCritical("ADSBDemodGUI::applyMapSettings: Map not found. Are all required Qt plugins installed?");
        return;
    }
    QObject *object = item->findChild("map");
    QGeoCoordinate coords;
    double zoom;
    if (object != nullptr)
    {
        // Save existing position of map
        coords = object->property("center").value();
        zoom = object->property("zoomLevel").value();
    }
    else
    {
        // Center on my location when map is first opened
        coords.setLatitude(stationLatitude);
        coords.setLongitude(stationLongitude);
        coords.setAltitude(stationAltitude);
        zoom = 10.0;
    }
    // Create the map using the specified provider
    QQmlProperty::write(item, "smoothing", MainCore::instance()->getSettings().getMapSmoothing());
    QQmlProperty::write(item, "aircraftMinZoomLevel", m_settings.m_aircraftMinZoom);
    QQmlProperty::write(item, "mapProvider", m_settings.m_mapProvider);
    QVariantMap parameters;
    QString mapType;
    if (m_settings.m_mapProvider == "osm")
    {
        // Use our repo, so we can append API key and redefine transmit maps
        parameters["osm.mapping.providersrepository.address"] = QString("http://127.0.0.1:%1/").arg(m_osmPort);
        // Use ADS-B specific cache, as we use different transmit maps
        QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/QtLocation/5.8/tiles/osm/sdrangel_adsb";
        parameters["osm.mapping.cache.directory"] = cachePath;
        // On Linux, we need to create the directory
        QDir dir(cachePath);
        if (!dir.exists()) {
            dir.mkpath(cachePath);
        }
        switch (m_settings.m_mapType)
        {
        case ADSBDemodSettings::AVIATION_LIGHT:
            mapType = "Transit Map";
            break;
        case ADSBDemodSettings::AVIATION_DARK:
            mapType = "Night Transit Map";
            break;
        case ADSBDemodSettings::STREET:
            mapType = "Street Map";
            break;
        case ADSBDemodSettings::SATELLITE:
            mapType = "Satellite Map";
            break;
        }
    }
    else if (m_settings.m_mapProvider == "mapboxgl")
    {
        switch (m_settings.m_mapType)
        {
        case ADSBDemodSettings::AVIATION_LIGHT:
            mapType = "mapbox://styles/mapbox/light-v9";
            break;
        case ADSBDemodSettings::AVIATION_DARK:
            mapType = "mapbox://styles/mapbox/dark-v9";
            break;
        case ADSBDemodSettings::STREET:
            mapType = "mapbox://styles/mapbox/streets-v10";
            break;
        case ADSBDemodSettings::SATELLITE:
            mapType = "mapbox://styles/mapbox/satellite-v9";
            break;
        }
    }
    QVariant retVal;
    if (!QMetaObject::invokeMethod(item, "createMap", Qt::DirectConnection,
                                Q_RETURN_ARG(QVariant, retVal),
                                Q_ARG(QVariant, QVariant::fromValue(parameters)),
                                Q_ARG(QVariant, mapType),
                                Q_ARG(QVariant, QVariant::fromValue(this))))
    {
        qCritical() << "ADSBDemodGUI::applyMapSettings - Failed to invoke createMap";
    }
    QObject *newMap = retVal.value();
    // Restore position of map
    if (newMap != nullptr)
    {
        if (coords.isValid())
        {
            newMap->setProperty("zoomLevel", QVariant::fromValue(zoom));
            newMap->setProperty("center", QVariant::fromValue(coords));
        }
    }
    else
    {
        qDebug() << "ADSBDemodGUI::applyMapSettings - createMap returned a nullptr";
    }
    // Move antenna icon to My Position
    QObject *stationObject = newMap->findChild("station");
    if(stationObject != NULL)
    {
        QGeoCoordinate coords = stationObject->property("coordinate").value();
        coords.setLatitude(stationLatitude);
        coords.setLongitude(stationLongitude);
        coords.setAltitude(stationAltitude);
        stationObject->setProperty("coordinate", QVariant::fromValue(coords));
        stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
    }
    else
    {
        qDebug() << "ADSBDemodGUI::applyMapSettings - Couldn't find station";
    }
}
// Called from QML when empty space clicked
void ADSBDemodGUI::clearHighlighted()
{
    highlightAircraft(nullptr);
}
ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
    ChannelGUI(parent),
    ui(new Ui::ADSBDemodGUI),
    m_pluginAPI(pluginAPI),
    m_deviceUISet(deviceUISet),
    m_channelMarker(this),
    m_deviceCenterFrequency(0),
    m_basebandSampleRate(1),
    m_basicSettingsShown(false),
    m_doApplySettings(true),
    m_tickCount(0),
    m_aircraftInfo(nullptr),
    m_airportModel(this),
    m_airspaceModel(this),
    m_trackAircraft(nullptr),
    m_highlightAircraft(nullptr),
    m_speech(nullptr),
    m_progressDialog(nullptr),
    m_loadingData(false)
{
    setAttribute(Qt::WA_DeleteOnClose, true);
    m_helpURL = "plugins/channelrx/demodadsb/readme.md";
    RollupContents *rollupContents = getRollupContents();
	ui->setupUi(rollupContents);
    setSizePolicy(rollupContents->sizePolicy());
    rollupContents->arrangeRollups();
	connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
    // Enable MSAA antialiasing on 2D map
    // This can be much faster than using layer.smooth in the QML, when there are many items
    // However, only seems to work when set to 16, and doesn't seem to be supported on all graphics cards
    int multisamples = MainCore::instance()->getSettings().getMapMultisampling();
    if (multisamples > 0)
    {
        QSurfaceFormat format;
        format.setSamples(multisamples);
        ui->map->setFormat(format);
    }
    m_osmPort = 0; // Pick a free port
    m_templateServer = new ADSBOSMTemplateServer("q2RVNAe3eFKCH4XsrE3r", m_osmPort);
    ui->map->setAttribute(Qt::WA_AcceptTouchEvents, true);
    ui->map->rootContext()->setContextProperty("aircraftModel", &m_aircraftModel);
    ui->map->rootContext()->setContextProperty("airportModel", &m_airportModel);
    ui->map->rootContext()->setContextProperty("airspaceModel", &m_airspaceModel);
    ui->map->rootContext()->setContextProperty("navAidModel", &m_navAidModel);
    ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map.qml")));
    connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
    m_adsbDemod = reinterpret_cast(rxChannel); //new ADSBDemod(m_deviceUISet->m_deviceSourceAPI);
    m_adsbDemod->setMessageQueueToGUI(getInputMessageQueue());
    connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
    CRightClickEnabler *feedRightClickEnabler = new CRightClickEnabler(ui->feed);
    connect(feedRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(feedSelect(const QPoint &)));
    ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
    ui->warning->setVisible(false);
    ui->warning->setStyleSheet("QLabel { background-color: red; }");
    ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
    ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
    ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
    m_channelMarker.blockSignals(true);
    m_channelMarker.setColor(Qt::red);
    m_channelMarker.setBandwidth(5000);
    m_channelMarker.setCenterFrequency(0);
    m_channelMarker.setTitle("ADS-B Demodulator");
    m_channelMarker.blockSignals(false);
    m_channelMarker.setVisible(true); // activate signal on the last setting only
    m_settings.setChannelMarker(&m_channelMarker);
    m_settings.setRollupState(&m_rollupState);
    m_deviceUISet->addChannelMarker(&m_channelMarker);
    connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
    connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
    connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
    // Set size of airline icons
    ui->adsbData->setIconSize(QSize(85, 20));
    // Resize the table using dummy data
    resizeTable();
    // Allow user to reorder columns
    ui->adsbData->horizontalHeader()->setSectionsMovable(true);
    // Allow user to sort table by clicking on headers
    ui->adsbData->setSortingEnabled(true);
    // Add context menu to allow hiding/showing of columns
    menu = new QMenu(ui->adsbData);
    for (int i = 0; i < ui->adsbData->horizontalHeader()->count(); i++)
    {
        QString text = ui->adsbData->horizontalHeaderItem(i)->text();
        menu->addAction(createCheckableItem(text, i, true));
    }
    ui->adsbData->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->adsbData->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint)));
    // Get signals when columns change
    connect(ui->adsbData->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(adsbData_sectionMoved(int, int, int)));
    connect(ui->adsbData->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(adsbData_sectionResized(int, int, int)));
    // Context menu
    ui->adsbData->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->adsbData, SIGNAL(customContextMenuRequested(QPoint)), SLOT(adsbData_customContextMenuRequested(QPoint)));
    TableTapAndHold *tableTapAndHold = new TableTapAndHold(ui->adsbData);
    connect(tableTapAndHold, &TableTapAndHold::tapAndHold, this, &ADSBDemodGUI::adsbData_customContextMenuRequested);
    ui->photoHeader->setVisible(false);
    ui->photoFlag->setVisible(false);
    ui->photo->setVisible(false);
    ui->flightDetails->setVisible(false);
    ui->aircraftDetails->setVisible(false);
    // Read aircraft information database, if it has previously been downloaded
    AircraftInformation::init();
    connect(&m_osnDB, &OsnDB::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
    connect(&m_osnDB, &OsnDB::downloadError, this, &ADSBDemodGUI::downloadError);
    connect(&m_osnDB, &OsnDB::downloadProgress, this, &ADSBDemodGUI::downloadProgress);
    connect(&m_osnDB, &OsnDB::downloadAircraftInformationFinished, this, &ADSBDemodGUI::downloadAircraftInformationFinished);
    m_aircraftInfo = OsnDB::getAircraftInformation();
    // Read airport information database, if it has previously been downloaded
    connect(&m_ourAirportsDB, &OurAirportsDB::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
    connect(&m_ourAirportsDB, &OurAirportsDB::downloadError, this, &ADSBDemodGUI::downloadError);
    connect(&m_ourAirportsDB, &OurAirportsDB::downloadProgress, this, &ADSBDemodGUI::downloadProgress);
    connect(&m_ourAirportsDB, &OurAirportsDB::downloadAirportInformationFinished, this, &ADSBDemodGUI::downloadAirportInformationFinished);
    m_airportInfo = OurAirportsDB::getAirportsById();
    // Read airspaces and NAVAIDs
    connect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
    connect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
    connect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
    connect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
    m_airspaces = OpenAIP::getAirspaces();
    m_navAids = OpenAIP::getNavAids();
    // Get station position
    Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
    Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
    Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
    m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
    // These are the default values in sdrbase/settings/preferences.cpp
    if ((stationLatitude == 49.012423) && (stationLongitude == 8.418125)) {
        ui->warning->setText("Please set your antenna location under Preferences > My Position");
    }
    // Get updated when position changes
    connect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &ADSBDemodGUI::preferenceChanged);
    // Get airport weather when requested
    connect(&m_airportModel, &AirportModel::requestMetar, this, &ADSBDemodGUI::requestMetar);
    // Add airports within range of My Position
    updateAirports();
    updateAirspaces();
    updateNavAids();
    update3DModels();
    m_flightInformation = nullptr;
    m_aviationWeather = nullptr;
    connect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
    connect(ui->photo, &ClickableLabel::clicked, this, &ADSBDemodGUI::photoClicked);
    // Update device list when devices are added or removed
    connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &ADSBDemodGUI::updateDeviceSetList);
    connect(MainCore::instance(), &MainCore::deviceSetRemoved, this, &ADSBDemodGUI::updateDeviceSetList);
    updateDeviceSetList();
    displaySettings();
    makeUIConnections();
    applySettings(true);
    connect(&m_importTimer, &QTimer::timeout, this, &ADSBDemodGUI::import);
    m_networkManager = new QNetworkAccessManager();
    QObject::connect(
        m_networkManager,
        &QNetworkAccessManager::finished,
        this,
        &ADSBDemodGUI::handleImportReply
    );
    applyImportSettings();
    connect(&m_redrawMapTimer, &QTimer::timeout, this, &ADSBDemodGUI::redrawMap);
    m_redrawMapTimer.setSingleShot(true);
    ui->map->installEventFilter(this);
    DialPopup::addPopupsToChildDials(this);
}
ADSBDemodGUI::~ADSBDemodGUI()
{
    if (m_templateServer)
    {
        m_templateServer->close();
        delete m_templateServer;
    }
    disconnect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
    disconnect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
    disconnect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
    disconnect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
    disconnect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
    disconnect(&m_redrawMapTimer, &QTimer::timeout, this, &ADSBDemodGUI::redrawMap);
    disconnect(&MainCore::instance()->getMasterTimer(), &QTimer::timeout, this, &ADSBDemodGUI::tick);
    m_redrawMapTimer.stop();
    // Remove aircraft from Map feature
    QHash::iterator i = m_aircraft.begin();
    while (i != m_aircraft.end())
    {
        Aircraft *aircraft = i.value();
        clearFromMap(QString("%1").arg(aircraft->m_icao, 0, 16));
        ++i;
    }
    delete ui;
    qDeleteAll(m_aircraft);
    if (m_flightInformation)
    {
        disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
        delete m_flightInformation;
    }
    delete m_aviationWeather;
    qDeleteAll(m_3DModelMatch);
    delete m_networkManager;
}
void ADSBDemodGUI::applySettings(bool force)
{
    if (m_doApplySettings)
    {
        qDebug() << "ADSBDemodGUI::applySettings";
        ADSBDemod::MsgConfigureADSBDemod* message = ADSBDemod::MsgConfigureADSBDemod::create(m_settings, force);
        m_adsbDemod->getInputMessageQueue()->push(message);
    }
}
void ADSBDemodGUI::displaySettings()
{
    m_channelMarker.blockSignals(true);
    m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
    m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
    m_channelMarker.setTitle(m_settings.m_title);
    m_channelMarker.blockSignals(false);
    m_channelMarker.setColor(m_settings.m_rgbColor);
    setTitleColor(m_settings.m_rgbColor);
    setWindowTitle(m_channelMarker.getTitle());
    setTitle(m_channelMarker.getTitle());
    blockApplySettings(true);
    ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
    ui->rfBWText->setText(QString("%1M").arg(m_settings.m_rfBandwidth / 1000000.0, 0, 'f', 1));
    ui->rfBW->setValue((int)m_settings.m_rfBandwidth);
    ui->spb->setCurrentIndex(m_settings.m_samplesPerBit/2-1);
    ui->correlateFullPreamble->setChecked(m_settings.m_correlateFullPreamble);
    ui->demodModeS->setChecked(m_settings.m_demodModeS);
    ui->thresholdText->setText(QString("%1").arg(m_settings.m_correlationThreshold, 0, 'f', 1));
    ui->threshold->setValue((int)(m_settings.m_correlationThreshold*10.0f));
    ui->phaseStepsText->setText(QString("%1").arg(m_settings.m_interpolatorPhaseSteps));
    ui->phaseSteps->setValue(m_settings.m_interpolatorPhaseSteps);
    ui->tapsPerPhaseText->setText(QString("%1").arg(m_settings.m_interpolatorTapsPerPhase, 0, 'f', 1));
    ui->tapsPerPhase->setValue((int)(m_settings.m_interpolatorTapsPerPhase*10.0f));
    // Enable these controls only for developers
    if (1)
    {
        ui->phaseStepsText->setVisible(false);
        ui->phaseSteps->setVisible(false);
        ui->tapsPerPhaseText->setVisible(false);
        ui->tapsPerPhase->setVisible(false);
    }
    ui->feed->setChecked(m_settings.m_feedEnabled);
    ui->flightPaths->setChecked(m_settings.m_flightPaths);
    m_aircraftModel.setFlightPaths(m_settings.m_flightPaths);
    ui->allFlightPaths->setChecked(m_settings.m_allFlightPaths);
    m_aircraftModel.setAllFlightPaths(m_settings.m_allFlightPaths);
    ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
    ui->logEnable->setChecked(m_settings.m_logEnabled);
    updateIndexLabel();
    QFont font(m_settings.m_tableFontName, m_settings.m_tableFontSize);
    ui->adsbData->setFont(font);
    // Set units in column headers
    if (m_settings.m_siUnits)
    {
        ui->adsbData->horizontalHeaderItem(ADSB_COL_ALTITUDE)->setText("Alt (m)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_VERTICALRATE)->setText("VR (m/s)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_SEL_ALTITUDE)->setText("Sel Alt (m)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_GROUND_SPEED)->setText("GS (kph)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_TRUE_AIRSPEED)->setText("TAS (kph)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_INDICATED_AIRSPEED)->setText("IAS (kph)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_HEADWIND)->setText("H Wnd (kph)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_WIND_SPEED)->setText("Wnd (kph)");
    }
    else
    {
        ui->adsbData->horizontalHeaderItem(ADSB_COL_ALTITUDE)->setText("Alt (ft)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_VERTICALRATE)->setText("VR (ft/m)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_SEL_ALTITUDE)->setText("Sel Alt (ft)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_GROUND_SPEED)->setText("GS (kn)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_TRUE_AIRSPEED)->setText("TAS (kn)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_INDICATED_AIRSPEED)->setText("IAS (kn)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_HEADWIND)->setText("H Wnd (kn)");
        ui->adsbData->horizontalHeaderItem(ADSB_COL_WIND_SPEED)->setText("Wnd (kn)");
    }
    // Order and size columns
    QHeaderView *header = ui->adsbData->horizontalHeader();
    for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
    {
        bool hidden = m_settings.m_columnSizes[i] == 0;
        header->setSectionHidden(i, hidden);
        menu->actions().at(i)->setChecked(!hidden);
        if (m_settings.m_columnSizes[i] > 0)
            ui->adsbData->setColumnWidth(i, m_settings.m_columnSizes[i]);
        header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]);
    }
    // Only update airports on map if settings have changed
    if ((m_airportInfo != nullptr)
        && ((m_settings.m_airportRange != m_currentAirportRange)
            || (m_settings.m_airportMinimumSize != m_currentAirportMinimumSize)
            || (m_settings.m_displayHeliports != m_currentDisplayHeliports)))
        updateAirports();
    updateAirspaces();
    updateNavAids();
    if (!m_settings.m_displayDemodStats)
        ui->stats->setText("");
    initFlightInformation();
    initAviationWeather();
    applyMapSettings();
    applyImportSettings();
    getRollupContents()->restoreState(m_rollupState);
    blockApplySettings(false);
    enableSpeechIfNeeded();
}
void ADSBDemodGUI::leaveEvent(QEvent* event)
{
    m_channelMarker.setHighlighted(false);
    ChannelGUI::leaveEvent(event);
}
void ADSBDemodGUI::enterEvent(EnterEventType* event)
{
    m_channelMarker.setHighlighted(true);
    ChannelGUI::enterEvent(event);
}
void ADSBDemodGUI::blockApplySettings(bool block)
{
    m_doApplySettings = !block;
}
void ADSBDemodGUI::tick()
{
    double magsqAvg, magsqPeak;
    int nbMagsqSamples;
    m_adsbDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
    double powDbAvg = CalcDb::dbPower(magsqAvg);
    double powDbPeak = CalcDb::dbPower(magsqPeak);
    ui->channelPowerMeter->levelChanged(
            (100.0f + powDbAvg) / 100.0f,
            (100.0f + powDbPeak) / 100.0f,
            nbMagsqSamples);
    if (m_tickCount % 4 == 0) {
        ui->channelPower->setText(tr("%1 dB").arg(powDbAvg, 0, 'f', 1));
    }
    m_tickCount++;
    // Tick is called 20x a second - lets check this every 10 seconds
    if (m_tickCount % (20*10) == 0)
    {
        // Remove aircraft that haven't been heard of for a user-defined time, as probably out of range
        QDateTime now = QDateTime::currentDateTime();
        qint64 nowSecs = now.toSecsSinceEpoch();
        QHash::iterator i = m_aircraft.begin();
        while (i != m_aircraft.end())
        {
            Aircraft *aircraft = i.value();
            qint64 secondsSinceLastFrame = nowSecs - aircraft->m_time.toSecsSinceEpoch();
            if (secondsSinceLastFrame >= m_settings.m_removeTimeout)
            {
                // Don't try to track it anymore
                if (m_trackAircraft == aircraft)
                {
                    m_adsbDemod->clearTarget();
                    m_trackAircraft = nullptr;
                }
                // Remove map model
                m_aircraftModel.removeAircraft(aircraft);
                // Remove row from table
                ui->adsbData->removeRow(aircraft->m_icaoItem->row());
                // Remove aircraft from hash
                i = m_aircraft.erase(i);
                // Remove from map feature
                clearFromMap(QString("%1").arg(aircraft->m_icao, 0, 16));
                // And finally free its memory
                delete aircraft;
            }
            else
            {
                ++i;
            }
        }
    }
}
void ADSBDemodGUI::resizeTable()
{
    // Fill table with a row of dummy data that will size the columns nicely
    int row = ui->adsbData->rowCount();
    ui->adsbData->setRowCount(row + 1);
    ui->adsbData->setItem(row, ADSB_COL_ICAO, new QTableWidgetItem("ICAO ID"));
    ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, new QTableWidgetItem("Callsign-"));
    ui->adsbData->setItem(row, ADSB_COL_MODEL, new QTableWidgetItem("Aircraft12345"));
    ui->adsbData->setItem(row, ADSB_COL_AIRLINE, new QTableWidgetItem("airbrigdecargo1"));
    ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, new QTableWidgetItem("Alt (ft)"));
    ui->adsbData->setItem(row, ADSB_COL_HEADING, new QTableWidgetItem("Hd (o)"));
    ui->adsbData->setItem(row, ADSB_COL_VERTICALRATE, new QTableWidgetItem("VR (ft/m)"));
    ui->adsbData->setItem(row, ADSB_COL_RANGE, new QTableWidgetItem("D (km)"));
    ui->adsbData->setItem(row, ADSB_COL_AZEL, new QTableWidgetItem("Az/El (o)"));
    ui->adsbData->setItem(row, ADSB_COL_LATITUDE, new QTableWidgetItem("-90.00000"));
    ui->adsbData->setItem(row, ADSB_COL_LONGITUDE, new QTableWidgetItem("-180.000000"));
    ui->adsbData->setItem(row, ADSB_COL_CATEGORY, new QTableWidgetItem("Heavy"));
    ui->adsbData->setItem(row, ADSB_COL_STATUS, new QTableWidgetItem("No emergency"));
    ui->adsbData->setItem(row, ADSB_COL_SQUAWK, new QTableWidgetItem("Squawk"));
    ui->adsbData->setItem(row, ADSB_COL_REGISTRATION, new QTableWidgetItem("G-12345"));
    ui->adsbData->setItem(row, ADSB_COL_COUNTRY, new QTableWidgetItem("Country"));
    ui->adsbData->setItem(row, ADSB_COL_REGISTERED, new QTableWidgetItem("Registered"));
    ui->adsbData->setItem(row, ADSB_COL_MANUFACTURER, new QTableWidgetItem("The Boeing Company"));
    ui->adsbData->setItem(row, ADSB_COL_OWNER, new QTableWidgetItem("British Airways"));
    ui->adsbData->setItem(row, ADSB_COL_OPERATOR_ICAO, new QTableWidgetItem("Operator"));
    ui->adsbData->setItem(row, ADSB_COL_TIME, new QTableWidgetItem("99:99:99"));
    ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, new QTableWidgetItem("Frames"));
    ui->adsbData->setItem(row, ADSB_COL_CORRELATION, new QTableWidgetItem("0.001/0.001/0.001"));
    ui->adsbData->setItem(row, ADSB_COL_RSSI, new QTableWidgetItem("-100.0"));
    ui->adsbData->setItem(row, ADSB_COL_FLIGHT_STATUS, new QTableWidgetItem("scheduled"));
    ui->adsbData->setItem(row, ADSB_COL_DEP, new QTableWidgetItem("WWWW"));
    ui->adsbData->setItem(row, ADSB_COL_ARR, new QTableWidgetItem("WWWW"));
    ui->adsbData->setItem(row, ADSB_COL_STD, new QTableWidgetItem("12:00 -1"));
    ui->adsbData->setItem(row, ADSB_COL_ETD, new QTableWidgetItem("12:00 -1"));
    ui->adsbData->setItem(row, ADSB_COL_ATD, new QTableWidgetItem("12:00 -1"));
    ui->adsbData->setItem(row, ADSB_COL_STA, new QTableWidgetItem("12:00 +1"));
    ui->adsbData->setItem(row, ADSB_COL_ETA, new QTableWidgetItem("12:00 +1"));
    ui->adsbData->setItem(row, ADSB_COL_ATA, new QTableWidgetItem("12:00 +1"));
    ui->adsbData->setItem(row, ADSB_COL_SEL_ALTITUDE, new QTableWidgetItem("Sel Alt (ft)"));
    ui->adsbData->setItem(row, ADSB_COL_SEL_HEADING, new QTableWidgetItem("Sel Hd (o)"));
    ui->adsbData->setItem(row, ADSB_COL_BARO, new QTableWidgetItem("Baro (mb)"));
    ui->adsbData->setItem(row, ADSB_COL_AP, new QTableWidgetItem("AP"));
    ui->adsbData->setItem(row, ADSB_COL_V_MODE, new QTableWidgetItem("V Mode"));
    ui->adsbData->setItem(row, ADSB_COL_L_MODE, new QTableWidgetItem("L Mode"));
    ui->adsbData->setItem(row, ADSB_COL_TRUE_AIRSPEED, new QTableWidgetItem("TAS (kn)"));
    ui->adsbData->setItem(row, ADSB_COL_INDICATED_AIRSPEED, new QTableWidgetItem("IAS (kn)"));
    ui->adsbData->setItem(row, ADSB_COL_MACH, new QTableWidgetItem("0.999"));
    ui->adsbData->setItem(row, ADSB_COL_HEADWIND, new QTableWidgetItem("H Wnd (kn)"));
    ui->adsbData->setItem(row, ADSB_COL_EST_AIR_TEMP, new QTableWidgetItem("OAT (C)"));
    ui->adsbData->setItem(row, ADSB_COL_WIND_SPEED, new QTableWidgetItem("Wnd (kn)"));
    ui->adsbData->setItem(row, ADSB_COL_WIND_DIR, new QTableWidgetItem("Wnd (o)"));
    ui->adsbData->setItem(row, ADSB_COL_STATIC_PRESSURE, new QTableWidgetItem("P (hPa)"));
    ui->adsbData->setItem(row, ADSB_COL_STATIC_AIR_TEMP, new QTableWidgetItem("T (C)"));
    ui->adsbData->setItem(row, ADSB_COL_HUMIDITY, new QTableWidgetItem("U (%)"));
    ui->adsbData->setItem(row, ADSB_COL_TIS_B, new QTableWidgetItem("TIS-B"));
    ui->adsbData->resizeColumnsToContents();
    ui->adsbData->setRowCount(row);
}
Aircraft* ADSBDemodGUI::findAircraftByFlight(const QString& flight)
{
    QHash::iterator i = m_aircraft.begin();
    while (i != m_aircraft.end())
    {
        Aircraft *aircraft = i.value();
        if (aircraft->m_flight == flight) {
            return aircraft;
        }
        ++i;
    }
    return nullptr;
}
// Convert to hh:mm (+/-days)
QString ADSBDemodGUI::dataTimeToShortString(QDateTime dt)
{
    if (dt.isValid())
    {
        QDate currentDate = QDateTime::currentDateTimeUtc().date();
        if (dt.date() == currentDate)
        {
            return dt.time().toString("hh:mm");
        }
        else
        {
            int days = currentDate.daysTo(dt.date());
            if (days >= 0) {
                return QString("%1 +%2").arg(dt.time().toString("hh:mm")).arg(days);
            } else {
                return QString("%1 %2").arg(dt.time().toString("hh:mm")).arg(days);
            }
        }
    }
    else
    {
        return "";
    }
}
void ADSBDemodGUI::initFlightInformation()
{
    if (m_flightInformation)
    {
        disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
        delete m_flightInformation;
        m_flightInformation = nullptr;
    }
    if (!m_settings.m_aviationstackAPIKey.isEmpty())
    {
        m_flightInformation = FlightInformation::create(m_settings.m_aviationstackAPIKey);
        if (m_flightInformation) {
            connect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
        }
    }
}
void ADSBDemodGUI::flightInformationUpdated(const FlightInformation::Flight& flight)
{
    Aircraft* aircraft = findAircraftByFlight(flight.m_flightICAO);
    if (aircraft)
    {
        aircraft->m_flightStatusItem->setText(flight.m_flightStatus);
        aircraft->m_depItem->setText(flight.m_departureICAO);
        aircraft->m_arrItem->setText(flight.m_arrivalICAO);
        aircraft->m_stdItem->setText(dataTimeToShortString(flight.m_departureScheduled));
        aircraft->m_etdItem->setText(dataTimeToShortString(flight.m_departureEstimated));
        aircraft->m_atdItem->setText(dataTimeToShortString(flight.m_departureActual));
        aircraft->m_staItem->setText(dataTimeToShortString(flight.m_arrivalScheduled));
        aircraft->m_etaItem->setText(dataTimeToShortString(flight.m_arrivalEstimated));
        aircraft->m_ataItem->setText(dataTimeToShortString(flight.m_arrivalActual));
        if (aircraft->m_positionValid) {
            m_aircraftModel.aircraftUpdated(aircraft);
        }
        updatePhotoFlightInformation(aircraft);
    }
    else
    {
        qDebug() << "ADSBDemodGUI::flightInformationUpdated - Flight not found in ADS-B table: " << flight.m_flightICAO;
    }
}
void ADSBDemodGUI::aircraftPhoto(const PlaneSpottersPhoto *photo)
{
    // Make sure the photo is for the currently highlighted aircraft, as it may
    // have taken a while to download
    if (!photo->m_pixmap.isNull() && m_highlightAircraft && (m_highlightAircraft->m_icaoItem->text() == photo->m_id))
    {
        ui->photo->setPixmap(photo->m_pixmap);
        ui->photo->setToolTip(QString("Photographer: %1").arg(photo->m_photographer)); // Required by terms of use
        ui->photoHeader->setVisible(true);
        ui->photoFlag->setVisible(true);
        ui->photo->setVisible(true);
        ui->flightDetails->setVisible(true);
        ui->aircraftDetails->setVisible(true);
        m_photoLink = photo->m_link;
    }
}
void ADSBDemodGUI::photoClicked()
{
    // Photo needs to link back to PlaneSpotters, as per terms of use
    if (m_highlightAircraft)
    {
        if (m_photoLink.isEmpty())
        {
            QString icaoUpper = QString("%1").arg(m_highlightAircraft->m_icao, 1, 16).toUpper();
            QDesktopServices::openUrl(QUrl(QString("https://www.planespotters.net/hex/%1").arg(icaoUpper)));
        }
        else
        {
            QDesktopServices::openUrl(QUrl(m_photoLink));
        }
    }
}
void ADSBDemodGUI::on_logEnable_clicked(bool checked)
{
    m_settings.m_logEnabled = checked;
    applySettings();
}
void ADSBDemodGUI::on_logFilename_clicked()
{
    // Get filename to save to
    QFileDialog fileDialog(nullptr, "Select file to log received frames to", "", "*.csv");
    fileDialog.setAcceptMode(QFileDialog::AcceptSave);
    if (fileDialog.exec())
    {
        QStringList fileNames = fileDialog.selectedFiles();
        if (fileNames.size() > 0)
        {
            m_settings.m_logFilename = fileNames[0];
            ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
            applySettings();
        }
    }
}
// Read .csv log and process as received frames
void ADSBDemodGUI::on_logOpen_clicked()
{
    QFileDialog fileDialog(nullptr, "Select .csv log file to read", "", "*.csv");
    if (fileDialog.exec())
    {
        QStringList fileNames = fileDialog.selectedFiles();
        if (fileNames.size() > 0)
        {
            QFile file(fileNames[0]);
            if (file.open(QIODevice::ReadOnly | QIODevice::Text))
            {
                QDateTime startTime = QDateTime::currentDateTime();
                m_loadingData = true;
                ui->adsbData->blockSignals(true);
                QTextStream in(&file);
                QString error;
                QHash colIndexes = CSV::readHeader(in, {"Data", "Correlation"}, error);
                if (error.isEmpty())
                {
                    int dataCol = colIndexes.value("Data");
                    int correlationCol = colIndexes.value("Correlation");
                    int maxCol = std::max(dataCol, correlationCol);
                    QMessageBox dialog(this);
                    dialog.setText("Reading ADS-B data");
                    dialog.addButton(QMessageBox::Cancel);
                    dialog.show();
                    QApplication::processEvents();
                    int count = 0;
                    int countOtherDF = 0;
                    bool cancelled = false;
                    QStringList cols;
                    crcadsb crc;
                    while (!cancelled && CSV::readRow(in, &cols))
                    {
                        if (cols.size() > maxCol)
                        {
                            QDateTime dateTime = QDateTime::currentDateTime(); // So they aren't removed immediately as too old
                            QByteArray bytes = QByteArray::fromHex(cols[dataCol].toLatin1());
                            float correlation = cols[correlationCol].toFloat();
                            int df = (bytes[0] >> 3) & ADS_B_DF_MASK; // Downlink format
                            if ((df == 4) || (df == 5) || (df == 17) || (df == 18) || (df == 20) || (df == 21))
                            {
                                int crcCalc = 0;
                                if ((df == 4) || (df == 5) || (df == 20) || (df == 21))  // handleADSB requires calculated CRC for Mode-S frames
                                {
                                    crc.init();
                                    crc.calculate((const uint8_t *)bytes.data(), bytes.size()-3);
                                    crcCalc = crc.get();
                                }
                                //qDebug() << "bytes.szie " << bytes.size() << " crc " << Qt::hex <<  crcCalc;
                                handleADSB(bytes, dateTime, correlation, correlation, crcCalc, false);
                                if ((count > 0) && (count % 100000 == 0))
                                {
                                    dialog.setText(QString("Reading ADS-B data\n%1 (Skipped %2)").arg(count).arg(countOtherDF));
                                    QApplication::processEvents();
                                    if (dialog.clickedButton()) {
                                        cancelled = true;
                                    }
                                }
                                count++;
                            }
                            else
                            {
                                countOtherDF++;
                            }
                        }
                    }
                    m_aircraftModel.allAircraftUpdated();
                    dialog.close();
                }
                else
                {
                    QMessageBox::critical(this, "ADS-B", error);
                }
                ui->adsbData->blockSignals(false);
                m_loadingData = false;
                if (m_settings.m_autoResizeTableColumns)
                    ui->adsbData->resizeColumnsToContents();
                ui->adsbData->setSortingEnabled(true);
                QDateTime finishTime = QDateTime::currentDateTime();
                qDebug() << "Read CSV in " << startTime.secsTo(finishTime);
            }
            else
            {
                QMessageBox::critical(this, "ADS-B", QString("Failed to open file %1").arg(fileNames[0]));
            }
        }
    }
}
void ADSBDemodGUI::downloadingURL(const QString& url)
{
    if (m_progressDialog)
    {
        m_progressDialog->setLabelText(QString("Downloading %1.").arg(url));
        m_progressDialog->setValue(m_progressDialog->value() + 1);
    }
}
void ADSBDemodGUI::downloadProgress(qint64 bytesRead, qint64 totalBytes)
{
    if (m_progressDialog)
    {
        m_progressDialog->setMaximum(totalBytes);
        m_progressDialog->setValue(bytesRead);
    }
}
void ADSBDemodGUI::downloadError(const QString& error)
{
    QMessageBox::critical(this, "ADS-B", error);
    if (m_progressDialog)
    {
        m_progressDialog->close();
        delete m_progressDialog;
        m_progressDialog = nullptr;
    }
}
void ADSBDemodGUI::downloadAirspaceFinished()
{
    if (m_progressDialog) {
        m_progressDialog->setLabelText("Reading airspaces.");
    }
    m_airspaces = OpenAIP::getAirspaces();
    updateAirspaces();
    m_openAIP.downloadNavAids();
}
void ADSBDemodGUI::downloadNavAidsFinished()
{
    if (m_progressDialog) {
        m_progressDialog->setLabelText("Reading NAVAIDs.");
    }
    m_navAids = OpenAIP::getNavAids();
    updateNavAids();
    if (m_progressDialog)
    {
        m_progressDialog->close();
        delete m_progressDialog;
        m_progressDialog = nullptr;
    }
}
void ADSBDemodGUI::downloadAircraftInformationFinished()
{
    if (m_progressDialog)
    {
        delete m_progressDialog;
        m_progressDialog = new QProgressDialog("Reading Aircraft Information.", "", 0, 1, this);
        m_progressDialog->setCancelButton(nullptr);
        m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
        m_progressDialog->setWindowModality(Qt::WindowModal);
        m_progressDialog->show();
        QApplication::processEvents();
    }
    m_aircraftInfo = OsnDB::getAircraftInformation();
    m_aircraftModel.updateAircraftInformation(m_aircraftInfo);
    if (m_progressDialog)
    {
        m_progressDialog->close();
        delete m_progressDialog;
        m_progressDialog = nullptr;
    }
}
void ADSBDemodGUI::downloadAirportInformationFinished()
{
    if (m_progressDialog)
    {
        delete m_progressDialog;
        m_progressDialog = new QProgressDialog("Reading Airport Information.", "", 0, 1, this);
        m_progressDialog->setCancelButton(nullptr);
        m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
        m_progressDialog->setWindowModality(Qt::WindowModal);
        m_progressDialog->show();
        QApplication::processEvents();
    }
    m_airportInfo = OurAirportsDB::getAirportsById();
    updateAirports();
    if (m_progressDialog)
    {
        m_progressDialog->close();
        delete m_progressDialog;
        m_progressDialog = nullptr;
    }
}
int ADSBDemodGUI::squawkDecode(int modeA) const
{
    int a, b, c, d;
    c = ((modeA >> 12) & 1) | ((modeA >> (10-1)) & 0x2) | ((modeA >> (8-2)) & 0x4);
    a = ((modeA >> 11) & 1) | ((modeA >> (9-1)) & 0x2) | ((modeA >> (7-2)) & 0x4);
    b = ((modeA >> 5) & 1) | ((modeA >> (3-1)) & 0x2) | ((modeA << (1)) & 0x4);
    d = ((modeA >> 4) & 1) | ((modeA >> (2-1)) & 0x2) | ((modeA << (2)) & 0x4);
    return a*1000 + b*100 + c*10 + d;
}
// https://en.wikipedia.org/wiki/Gillham_code
int ADSBDemodGUI::gillhamToFeet(int n) const
{
    int c1 = (n >> 10) & 1;
    int a1 = (n >> 9) & 1;
    int c2 = (n >> 8) & 1;
    int a2 = (n >> 7) & 1;
    int c4 = (n >> 6) & 1;
    int a4 = (n >> 5) & 1;
    int b1 = (n >> 4) & 1;
    int b2 = (n >> 3) & 1;
    int d2 = (n >> 2) & 1;
    int b4 = (n >> 1) & 1;
    int d4 = n & 1;
    int n500 = grayToBinary((d2 << 7) | (d4 << 6) | (a1 << 5) | (a2 << 4) | (a4 << 3) | (b1 << 2) | (b2 << 1) | b4, 4);
    int n100 = grayToBinary((c1 << 2) | (c2 << 1) | c4, 3) - 1;
    if (n100 == 6) {
        n100 = 4;
    }
    if (n500 %2 != 0) {
        n100 = 4 - n100;
    }
    return -1200 + n500*500 + n100*100;
}
int ADSBDemodGUI::grayToBinary(int gray, int bits) const
{
    int binary = 0;
    for (int i = bits - 1; i >= 0; i--) {
        binary = binary | ((((1 << (i+1)) & binary) >> 1) ^ ((1 << i) & gray));
    }
    return binary;
}
void ADSBDemodGUI::redrawMap()
{
    // An awful workaround for https://bugreports.qt.io/browse/QTBUG-100333
    // Also used in Map feature
    QQuickItem *item = ui->map->rootObject();
    if (item)
    {
        QObject *object = item->findChild("map");
        if (object)
        {
            double zoom = object->property("zoomLevel").value();
            object->setProperty("zoomLevel", QVariant::fromValue(zoom+1));
            object->setProperty("zoomLevel", QVariant::fromValue(zoom));
        }
    }
}
void ADSBDemodGUI::showEvent(QShowEvent *event)
{
    if (!event->spontaneous())
    {
        // Workaround for https://bugreports.qt.io/browse/QTBUG-100333
        // MapQuickItems can be in wrong position when window is first displayed
        m_redrawMapTimer.start(500);
    }
    ChannelGUI::showEvent(event);
}
bool ADSBDemodGUI::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == ui->map)
    {
        if (event->type() == QEvent::Resize)
        {
            // Workaround for https://bugreports.qt.io/browse/QTBUG-100333
            // MapQuickItems can be in wrong position after vertical resize
            QResizeEvent *resizeEvent = static_cast(event);
            QSize oldSize = resizeEvent->oldSize();
            QSize size = resizeEvent->size();
            if (oldSize.height() != size.height()) {
                redrawMap();
            }
        }
    }
    return ChannelGUI::eventFilter(obj, event);
}
void ADSBDemodGUI::applyImportSettings()
{
    m_importTimer.setInterval(m_settings.m_importPeriod * 1000);
    if (m_settings.m_feedEnabled && m_settings.m_importEnabled) {
        m_importTimer.start();
    } else {
        m_importTimer.stop();
    }
}
// Import ADS-B data from opensky-network via an API call
void ADSBDemodGUI::import()
{
    QString urlString = "https://";
    if (!m_settings.m_importUsername.isEmpty() && !m_settings.m_importPassword.isEmpty()) {
        urlString = urlString + m_settings.m_importUsername + ":" + m_settings.m_importPassword + "@";
    }
    urlString = urlString + m_settings.m_importHost + "/api/states/all";
    QChar join = '?';
    if (!m_settings.m_importParameters.isEmpty())
    {
        urlString = urlString + join + m_settings.m_importParameters;
        join = '&';
    }
    if (!m_settings.m_importMinLatitude.isEmpty())
    {
        urlString = urlString + join + "lamin=" + m_settings.m_importMinLatitude;
        join = '&';
    }
    if (!m_settings.m_importMaxLatitude.isEmpty())
    {
        urlString = urlString + join + "lamax=" + m_settings.m_importMaxLatitude;
        join = '&';
    }
    if (!m_settings.m_importMinLongitude.isEmpty())
    {
        urlString = urlString + join + "lomin=" + m_settings.m_importMinLongitude;
        join = '&';
    }
    if (!m_settings.m_importMaxLongitude.isEmpty())
    {
        urlString = urlString + join + "lomax=" + m_settings.m_importMaxLongitude;
        join = '&';
    }
    m_networkManager->get(QNetworkRequest(QUrl(urlString)));
}
// Handle opensky-network API call reply
void ADSBDemodGUI::handleImportReply(QNetworkReply* reply)
{
    if (reply)
    {
        if (!reply->error())
        {
            QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
            if (document.isObject())
            {
                QJsonObject obj = document.object();
                if (obj.contains("time") && obj.contains("states"))
                {
                    int seconds = obj.value("time").toInt();
                    QDateTime dateTime = QDateTime::fromSecsSinceEpoch(seconds);
                    QJsonArray states = obj.value("states").toArray();
                    for (int i = 0; i < states.size(); i++)
                    {
                        QJsonArray state = states[i].toArray();
                        int icao = state[0].toString().toInt(nullptr, 16);
                        bool newAircraft;
                        Aircraft *aircraft = getAircraft(icao, newAircraft);
                        QString callsign = state[1].toString().trimmed();
                        if (!callsign.isEmpty())
                        {
                            aircraft->m_callsign = callsign;
                            aircraft->m_callsignItem->setText(aircraft->m_callsign);
                        }
                        QDateTime timePosition = dateTime;
                        if (state[3].isNull()) {
                            timePosition = dateTime.addSecs(-15); // At least 15 seconds old
                        } else {
                            timePosition = QDateTime::fromSecsSinceEpoch(state[3].toInt());
                        }
                        aircraft->m_time = QDateTime::fromSecsSinceEpoch(state[4].toInt());
                        QTime time = aircraft->m_time.time();
                        aircraft->m_timeItem->setText(QString("%1:%2:%3").arg(time.hour(), 2, 10, QLatin1Char('0')).arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
                        aircraft->m_adsbFrameCount++;
                        aircraft->m_adsbFrameCountItem->setData(Qt::DisplayRole, aircraft->m_adsbFrameCount);
                        if (timePosition > aircraft->m_positionDateTime)
                        {
                            if (!state[5].isNull() && !state[6].isNull())
                            {
                                aircraft->m_longitude = state[5].toDouble();
                                aircraft->m_latitude = state[6].toDouble();
                                aircraft->m_longitudeItem->setData(Qt::DisplayRole, aircraft->m_longitude);
                                aircraft->m_latitudeItem->setData(Qt::DisplayRole, aircraft->m_latitude);
                                updatePosition(aircraft);
                                aircraft->m_cprValid[0] = false;
                                aircraft->m_cprValid[1] = false;
                            }
                            if (!state[7].isNull())
                            {
                                aircraft->m_altitude = (int)Units::metresToFeet(state[7].toDouble());
                                aircraft->m_altitudeValid = true;
                                aircraft->m_altitudeGNSS = false;
                                aircraft->m_altitudeItem->setData(Qt::DisplayRole, aircraft->m_altitude);
                            }
                            if (!state[5].isNull() && !state[6].isNull() && !state[7].isNull())
                            {
                                QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude);
                                aircraft->m_coordinates.push_back(QVariant::fromValue(coord));
                            }
                            aircraft->m_positionDateTime = timePosition;
                        }
                        aircraft->m_onSurface = state[8].toBool(false);
                        if (!state[9].isNull())
                        {
                            aircraft->m_groundspeed = (int)state[9].toDouble();
                            aircraft->m_groundspeedItem->setData(Qt::DisplayRole, aircraft->m_groundspeed);
                            aircraft->m_groundspeedValid = true;
                        }
                        if (!state[10].isNull())
                        {
                            aircraft->m_heading = (float)state[10].toDouble();
                            aircraft->m_headingItem->setData(Qt::DisplayRole, std::round(aircraft->m_heading));
                            aircraft->m_headingValid = true;
                            aircraft->m_headingDateTime = aircraft->m_time;
                        }
                        if (!state[11].isNull())
                        {
                            aircraft->m_verticalRate = (int)state[10].toDouble();
                            aircraft->m_verticalRateItem->setData(Qt::DisplayRole, aircraft->m_verticalRate);
                            aircraft->m_verticalRateValid = true;
                        }
                        if (!state[14].isNull())
                        {
                            aircraft->m_squawk = state[14].toString().toInt();
                            aircraft->m_squawkItem->setText(QString("%1").arg(aircraft->m_squawk, 4, 10, QLatin1Char('0')));
                        }
                        // Update aircraft in map
                        if (aircraft->m_positionValid)
                        {
                            // Check to see if we need to start any animations
                            QList *animations = animate(dateTime, aircraft);
                            // Update map displayed in channel
                            m_aircraftModel.aircraftUpdated(aircraft);
                            // Send to Map feature
                            sendToMap(aircraft, animations);
                        }
                        // Check to see if we need to emit a notification about this aircraft
                        checkDynamicNotification(aircraft);
                    }
                }
                else
                {
                    qDebug() << "ADSBDemodGUI::handleImportReply: Document object does not contain time and states: " << document;
                }
            }
            else
            {
                qDebug() << "ADSBDemodGUI::handleImportReply: Document is not an object: " << document;
            }
        }
        else
        {
            qDebug() << "ADSBDemodGUI::handleImportReply: error " << reply->error();
        }
        reply->deleteLater();
    }
}
void ADSBDemodGUI::preferenceChanged(int elementType)
{
    Preferences::ElementType pref = (Preferences::ElementType)elementType;
    if ((pref == Preferences::Latitude) || (pref == Preferences::Longitude) || (pref == Preferences::Altitude))
    {
        Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
        Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
        Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
        QGeoCoordinate stationPosition(stationLatitude, stationLongitude, stationAltitude);
        QGeoCoordinate previousPosition(m_azEl.getLocationSpherical().m_latitude, m_azEl.getLocationSpherical().m_longitude, m_azEl.getLocationSpherical().m_altitude);
        if (stationPosition != previousPosition)
        {
            m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
            // Update distances and what is visible, but only do it if position has changed significantly
            if (!m_lastFullUpdatePosition.isValid() || (stationPosition.distanceTo(m_lastFullUpdatePosition) >= 1000))
            {
                updateAirports();
                updateAirspaces();
                updateNavAids();
                m_lastFullUpdatePosition = stationPosition;
            }
            // Update icon position on Map
            QQuickItem *item = ui->map->rootObject();
            QObject *map = item->findChild("map");
            if (map != nullptr)
            {
                QObject *stationObject = map->findChild("station");
                if(stationObject != NULL)
                {
                    QGeoCoordinate coords = stationObject->property("coordinate").value();
                    coords.setLatitude(stationLatitude);
                    coords.setLongitude(stationLongitude);
                    coords.setAltitude(stationAltitude);
                    stationObject->setProperty("coordinate", QVariant::fromValue(coords));
                }
            }
        }
    }
    else if (pref == Preferences::StationName)
    {
        // Update icon label on Map
        QQuickItem *item = ui->map->rootObject();
        QObject *map = item->findChild("map");
        if (map != nullptr)
        {
            QObject *stationObject = map->findChild("station");
            if(stationObject != NULL) {
                stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
            }
        }
    }
    else if (pref == Preferences::MapSmoothing)
    {
        QQuickItem *item = ui->map->rootObject();
        QQmlProperty::write(item, "smoothing", MainCore::instance()->getSettings().getMapSmoothing());
    }
}
void ADSBDemodGUI::initAviationWeather()
{
    if (m_aviationWeather)
    {
        disconnect(m_aviationWeather, &AviationWeather::weatherUpdated, this, &ADSBDemodGUI::weatherUpdated);
        delete m_aviationWeather;
        m_aviationWeather = nullptr;
    }
    if (!m_settings.m_checkWXAPIKey.isEmpty())
    {
        m_aviationWeather = AviationWeather::create(m_settings.m_checkWXAPIKey);
        if (m_aviationWeather) {
            connect(m_aviationWeather, &AviationWeather::weatherUpdated, this, &ADSBDemodGUI::weatherUpdated);
        }
    }
}
void ADSBDemodGUI::requestMetar(const QString& icao)
{
    if (m_aviationWeather) {
        m_aviationWeather->getWeather(icao);
    }
}
void ADSBDemodGUI::weatherUpdated(const AviationWeather::METAR &metar)
{
    m_airportModel.updateWeather(metar.m_icao, metar.m_text, metar.decoded());
}
void ADSBDemodGUI::makeUIConnections()
{
    QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &ADSBDemodGUI::on_deltaFrequency_changed);
    QObject::connect(ui->rfBW, &QSlider::valueChanged, this, &ADSBDemodGUI::on_rfBW_valueChanged);
    QObject::connect(ui->threshold, &QDial::valueChanged, this, &ADSBDemodGUI::on_threshold_valueChanged);
    QObject::connect(ui->phaseSteps, &QDial::valueChanged, this, &ADSBDemodGUI::on_phaseSteps_valueChanged);
    QObject::connect(ui->tapsPerPhase, &QDial::valueChanged, this, &ADSBDemodGUI::on_tapsPerPhase_valueChanged);
    QObject::connect(ui->adsbData, &QTableWidget::cellClicked, this, &ADSBDemodGUI::on_adsbData_cellClicked);
    QObject::connect(ui->adsbData, &QTableWidget::cellDoubleClicked, this, &ADSBDemodGUI::on_adsbData_cellDoubleClicked);
    QObject::connect(ui->spb, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_spb_currentIndexChanged);
    QObject::connect(ui->correlateFullPreamble, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_correlateFullPreamble_clicked);
    QObject::connect(ui->demodModeS, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_demodModeS_clicked);
    QObject::connect(ui->feed, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_feed_clicked);
    QObject::connect(ui->notifications, &QToolButton::clicked, this, &ADSBDemodGUI::on_notifications_clicked);
    QObject::connect(ui->flightInfo, &QToolButton::clicked, this, &ADSBDemodGUI::on_flightInfo_clicked);
    QObject::connect(ui->findOnMapFeature, &QToolButton::clicked, this, &ADSBDemodGUI::on_findOnMapFeature_clicked);
    QObject::connect(ui->getOSNDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getOSNDB_clicked);
    QObject::connect(ui->getAirportDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getAirportDB_clicked);
    QObject::connect(ui->getAirspacesDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getAirspacesDB_clicked);
    QObject::connect(ui->flightPaths, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_flightPaths_clicked);
    QObject::connect(ui->allFlightPaths, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_allFlightPaths_clicked);
    QObject::connect(ui->device, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_device_currentIndexChanged);
    QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &ADSBDemodGUI::on_displaySettings_clicked);
    QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_logEnable_clicked);
    QObject::connect(ui->logFilename, &QToolButton::clicked, this, &ADSBDemodGUI::on_logFilename_clicked);
    QObject::connect(ui->logOpen, &QToolButton::clicked, this, &ADSBDemodGUI::on_logOpen_clicked);
}
void ADSBDemodGUI::updateAbsoluteCenterFrequency()
{
    setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
} |