mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-26 17:58:43 -05:00
41fca9d2fd
Add additional unit conversions.
1421 lines
44 KiB
C++
1421 lines
44 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
|
|
// Copyright (C) 2020 Jon Beniston, M7RCE //
|
|
// //
|
|
// This program is free software; you can redistribute it and/or modify //
|
|
// it under the terms of the GNU General Public License as published by //
|
|
// the Free Software Foundation as version 3 of the License, or //
|
|
// (at your option) any later version. //
|
|
// //
|
|
// This program is distributed in the hope that it will be useful, //
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
|
// GNU General Public License V3 for more details. //
|
|
// //
|
|
// You should have received a copy of the GNU General Public License //
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <limits>
|
|
#include <QDockWidget>
|
|
#include <QMainWindow>
|
|
#include <QDebug>
|
|
#include <QQuickItem>
|
|
#include <QGeoLocation>
|
|
#include <QGeoCoordinate>
|
|
#include <QQmlContext>
|
|
#include <QMessageBox>
|
|
#include <QAction>
|
|
|
|
#include "vordemodgui.h"
|
|
|
|
#include "device/deviceuiset.h"
|
|
#include "dsp/dspengine.h"
|
|
#include "dsp/dspcommands.h"
|
|
#include "ui_vordemodgui.h"
|
|
#include "plugin/pluginapi.h"
|
|
#include "util/simpleserializer.h"
|
|
#include "util/db.h"
|
|
#include "util/morse.h"
|
|
#include "util/units.h"
|
|
#include "gui/basicchannelsettingsdialog.h"
|
|
#include "gui/devicestreamselectiondialog.h"
|
|
#include "dsp/dspengine.h"
|
|
#include "gui/crightclickenabler.h"
|
|
#include "gui/audioselectdialog.h"
|
|
#include "channel/channelwebapiutils.h"
|
|
#include "maincore.h"
|
|
|
|
#include "vordemod.h"
|
|
#include "vordemodreport.h"
|
|
#include "vordemodsink.h"
|
|
|
|
#define VOR_COL_NAME 0
|
|
#define VOR_COL_FREQUENCY 1
|
|
#define VOR_COL_OFFSET 2
|
|
#define VOR_COL_IDENT 3
|
|
#define VOR_COL_MORSE 4
|
|
#define VOR_COL_RX_IDENT 5
|
|
#define VOR_COL_RX_MORSE 6
|
|
#define VOR_COL_RADIAL 7
|
|
#define VOR_COL_REF_MAG 8
|
|
#define VOR_COL_VAR_MAG 9
|
|
#define VOR_COL_MUTE 10
|
|
|
|
static const char *countryCodes[] = {
|
|
"ad",
|
|
"ae",
|
|
"af",
|
|
"ag",
|
|
"ai",
|
|
"al",
|
|
"am",
|
|
"an",
|
|
"ao",
|
|
"aq",
|
|
"ar",
|
|
"as",
|
|
"at",
|
|
"au",
|
|
"aw",
|
|
"ax",
|
|
"az",
|
|
"ba",
|
|
"bb",
|
|
"bd",
|
|
"be",
|
|
"bf",
|
|
"bg",
|
|
"bh",
|
|
"bi",
|
|
"bj",
|
|
"bl",
|
|
"bm",
|
|
"bn",
|
|
"bo",
|
|
"bq",
|
|
"br",
|
|
"bs",
|
|
"bt",
|
|
"bv",
|
|
"bw",
|
|
"by",
|
|
"bz",
|
|
"ca",
|
|
"cc",
|
|
"cd",
|
|
"cf",
|
|
"cg",
|
|
"ch",
|
|
"ci",
|
|
"ck",
|
|
"cl",
|
|
"cm",
|
|
"cn",
|
|
"co",
|
|
"cr",
|
|
"cu",
|
|
"cv",
|
|
"cw",
|
|
"cx",
|
|
"cy",
|
|
"cz",
|
|
"de",
|
|
"dj",
|
|
"dk",
|
|
"dm",
|
|
"do",
|
|
"dz",
|
|
"ec",
|
|
"ee",
|
|
"eg",
|
|
"eh",
|
|
"er",
|
|
"es",
|
|
"et",
|
|
"fi",
|
|
"fj",
|
|
"fk",
|
|
"fm",
|
|
"fo",
|
|
"fr",
|
|
"ga",
|
|
"gb",
|
|
"ge",
|
|
"gf",
|
|
"gg",
|
|
"gh",
|
|
"gi",
|
|
"gl",
|
|
"gm",
|
|
"gn",
|
|
"gp",
|
|
"gq",
|
|
"gr",
|
|
"gs",
|
|
"gt",
|
|
"gu",
|
|
"gw",
|
|
"gy",
|
|
"hk",
|
|
"hm",
|
|
"hn",
|
|
"hr",
|
|
"hu",
|
|
"id",
|
|
"ie",
|
|
"il",
|
|
"im",
|
|
"in",
|
|
"io",
|
|
"iq",
|
|
"ir",
|
|
"is",
|
|
"it",
|
|
"je",
|
|
"jm",
|
|
"jo",
|
|
"jp",
|
|
"ke",
|
|
"kg",
|
|
"kh",
|
|
"ki",
|
|
"km",
|
|
"kn",
|
|
"kp",
|
|
"kr",
|
|
"kw",
|
|
"ky",
|
|
"kz",
|
|
"la",
|
|
"lb",
|
|
"lc",
|
|
"li",
|
|
"lk",
|
|
"lr",
|
|
"ls",
|
|
"lt",
|
|
"lu",
|
|
"lv",
|
|
"ly",
|
|
"ma",
|
|
"mc",
|
|
"md",
|
|
"me",
|
|
"mf",
|
|
"mg",
|
|
"mh",
|
|
"mk",
|
|
"ml",
|
|
"mm",
|
|
"mn",
|
|
"mo",
|
|
"mp",
|
|
"mq",
|
|
"mr",
|
|
"ms",
|
|
"mt",
|
|
"mu",
|
|
"mv",
|
|
"mw",
|
|
"mx",
|
|
"my",
|
|
"mz",
|
|
"na",
|
|
"nc",
|
|
"ne",
|
|
"nf",
|
|
"ng",
|
|
"ni",
|
|
"nl",
|
|
"no",
|
|
"np",
|
|
"nr",
|
|
"nu",
|
|
"nz",
|
|
"om",
|
|
"pa",
|
|
"pe",
|
|
"pf",
|
|
"pg",
|
|
"ph",
|
|
"pk",
|
|
"pl",
|
|
"pm",
|
|
"pn",
|
|
"pr",
|
|
"ps",
|
|
"pt",
|
|
"pw",
|
|
"py",
|
|
"qa",
|
|
"re",
|
|
"ro",
|
|
"rs",
|
|
"ru",
|
|
"rw",
|
|
"sa",
|
|
"sb",
|
|
"sc",
|
|
"sd",
|
|
"se",
|
|
"sg",
|
|
"sh",
|
|
"si",
|
|
"sj",
|
|
"sk",
|
|
"sl",
|
|
"sm",
|
|
"sn",
|
|
"so",
|
|
"sr",
|
|
"ss",
|
|
"st",
|
|
"sv",
|
|
"sx",
|
|
"sy",
|
|
"sz",
|
|
"tc",
|
|
"td",
|
|
"tf",
|
|
"tg",
|
|
"th",
|
|
"tj",
|
|
"tk",
|
|
"tl",
|
|
"tm",
|
|
"tn",
|
|
"to",
|
|
"tr",
|
|
"tt",
|
|
"tv",
|
|
"tw",
|
|
"tz",
|
|
"ua",
|
|
"ug",
|
|
"um",
|
|
"us",
|
|
"uy",
|
|
"uz",
|
|
"va",
|
|
"vc",
|
|
"ve",
|
|
"vg",
|
|
"vi",
|
|
"vn",
|
|
"vu",
|
|
"wf",
|
|
"ws",
|
|
"ye",
|
|
"yt",
|
|
"za",
|
|
"zm",
|
|
"zw",
|
|
nullptr
|
|
};
|
|
|
|
// Lats and longs in decimal degrees. Distance in metres. Bearing in degrees.
|
|
// https://www.movable-type.co.uk/scripts/latlong.html
|
|
static void calcRadialEndPoint(float startLatitude, float startLongitude, float distance, float bearing, float &endLatitude, float &endLongitude)
|
|
{
|
|
double startLatRad = startLatitude*M_PI/180.0;
|
|
double startLongRad = startLongitude*M_PI/180.0;
|
|
double theta = bearing*M_PI/180.0;
|
|
double earthRadius = 6378137.0; // At equator
|
|
double delta = distance/earthRadius;
|
|
double endLatRad = std::asin(sin(startLatRad)*cos(delta) + cos(startLatRad)*sin(delta)*cos(theta));
|
|
double endLongRad = startLongRad + std::atan2(sin(theta)*sin(delta)*cos(startLatRad), cos(delta) - sin(startLatRad)*sin(endLatRad));
|
|
endLatitude = endLatRad*180.0/M_PI;
|
|
endLongitude = endLongRad*180.0/M_PI;
|
|
}
|
|
|
|
// Calculate intersection point along two radials
|
|
// https://www.movable-type.co.uk/scripts/latlong.html
|
|
static bool calcIntersectionPoint(float lat1, float lon1, float bearing1, float lat2, float lon2, float bearing2, float &intersectLat, float &intersectLon)
|
|
{
|
|
|
|
double lat1Rad = Units::degreesToRadians(lat1);
|
|
double lon1Rad = Units::degreesToRadians(lon1);
|
|
double lat2Rad = Units::degreesToRadians(lat2);
|
|
double lon2Rad = Units::degreesToRadians(lon2);
|
|
double theta13 = Units::degreesToRadians(bearing1);
|
|
double theta23 = Units::degreesToRadians(bearing2);
|
|
|
|
double deltaLat = lat1Rad - lat2Rad;
|
|
double deltaLon = lon1Rad - lon2Rad;
|
|
double sindlat = sin(deltaLat/2.0);
|
|
double sindlon = sin(deltaLon/2.0);
|
|
double cosLat1 = cos(lat1Rad);
|
|
double cosLat2 = cos(lat2Rad);
|
|
double delta12 = 2.0 * asin(sqrt(sindlat*sindlat+cosLat1*cosLat2*sindlon*sindlon));
|
|
if (abs(delta12) < std::numeric_limits<float>::epsilon())
|
|
return false;
|
|
|
|
double sinLat1 = sin(lat1Rad);
|
|
double sinLat2 = sin(lat2Rad);
|
|
double sinDelta12 = sin(delta12);
|
|
double cosDelta12 = cos(delta12);
|
|
double thetaA = acos((sinLat2-sinLat1*cosDelta12)/(sinDelta12*cosLat1));
|
|
double thetaB = acos((sinLat1-sinLat2*cosDelta12)/(sinDelta12*cosLat2));
|
|
|
|
double theta12, theta21;
|
|
if (sin(lon2Rad-lon1Rad) > 0.0)
|
|
{
|
|
theta12 = thetaA;
|
|
theta21 = 2.0*M_PI-thetaB;
|
|
}
|
|
else
|
|
{
|
|
theta12 = 2.0*M_PI-thetaA;
|
|
theta21 = thetaB;
|
|
}
|
|
double alpha1 = theta13 - theta12;
|
|
double alpha2 = theta21 - theta23;
|
|
double sinAlpha1 = sin(alpha1);
|
|
double sinAlpha2 = sin(alpha2);
|
|
if ((sinAlpha1 == 0.0) && (sinAlpha2 == 0.0))
|
|
return false;
|
|
if (sinAlpha1*sinAlpha2 < 0.0)
|
|
return false;
|
|
double cosAlpha1 = cos(alpha1);
|
|
double cosAlpha2 = cos(alpha2);
|
|
double cosAlpha3 = -cosAlpha1*cosAlpha2+sinAlpha1*sinAlpha2*cos(delta12);
|
|
double delta13 = atan2(sin(delta12)*sinAlpha1*sinAlpha2, cosAlpha2+cosAlpha1*cosAlpha3);
|
|
double lat3Rad = asin(sinLat1*cos(delta13)+cosLat1*sin(delta13)*cos(theta13));
|
|
double lon3Rad = lon1Rad + atan2(sin(theta13)*sin(delta13)*cosLat1, cos(delta13)-sinLat1*sin(lat3Rad));
|
|
|
|
intersectLat = Units::radiansToDegrees(lat3Rad);
|
|
intersectLon = Units::radiansToDegrees(lon3Rad);
|
|
|
|
return true;
|
|
}
|
|
|
|
VORGUI::VORGUI(NavAid *navAid, VORDemodGUI *gui) :
|
|
m_navAid(navAid),
|
|
m_gui(gui)
|
|
{
|
|
// These are deleted by QTableWidget
|
|
m_nameItem = new QTableWidgetItem();
|
|
m_frequencyItem = new QTableWidgetItem();
|
|
m_offsetItem = new QTableWidgetItem();
|
|
m_radialItem = new QTableWidgetItem();
|
|
m_identItem = new QTableWidgetItem();
|
|
m_morseItem = new QTableWidgetItem();
|
|
m_rxIdentItem = new QTableWidgetItem();
|
|
m_rxMorseItem = new QTableWidgetItem();
|
|
m_varMagItem = new QTableWidgetItem();
|
|
m_refMagItem = new QTableWidgetItem();
|
|
|
|
m_muteItem = new QWidget();
|
|
|
|
m_muteButton = new QToolButton();
|
|
m_muteButton->setCheckable(true);
|
|
m_muteButton->setChecked(false);
|
|
m_muteButton->setToolTip("Mute/unmute audio from this VOR");
|
|
m_muteButton->setIcon(m_gui->m_muteIcon);
|
|
|
|
QHBoxLayout* pLayout = new QHBoxLayout(m_muteItem);
|
|
pLayout->addWidget(m_muteButton);
|
|
pLayout->setAlignment(Qt::AlignCenter);
|
|
pLayout->setContentsMargins(0, 0, 0, 0);
|
|
m_muteItem->setLayout(pLayout);
|
|
|
|
connect(m_muteButton, &QPushButton::toggled, this, &VORGUI::on_audioMute_toggled);
|
|
|
|
m_coordinates.push_back(QVariant::fromValue(*new QGeoCoordinate(m_navAid->m_latitude, m_navAid->m_longitude, Units::feetToMetres(m_navAid->m_elevation))));
|
|
}
|
|
|
|
void VORGUI::on_audioMute_toggled(bool checked)
|
|
{
|
|
m_gui->m_settings.m_subChannelSettings.value(m_navAid->m_id)->m_audioMute = checked;
|
|
m_gui->applySettings();
|
|
}
|
|
|
|
QVariant VORModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
int row = index.row();
|
|
if ((row < 0) || (row >= m_vors.count()))
|
|
return QVariant();
|
|
if (role == VORModel::positionRole)
|
|
{
|
|
// Coordinates to display the VOR icon at
|
|
QGeoCoordinate coords;
|
|
coords.setLatitude(m_vors[row]->m_latitude);
|
|
coords.setLongitude(m_vors[row]->m_longitude);
|
|
coords.setAltitude(Units::feetToMetres(m_vors[row]->m_elevation));
|
|
return QVariant::fromValue(coords);
|
|
}
|
|
else if (role == VORModel::vorDataRole)
|
|
{
|
|
// Create the text to go in the bubble next to the VOR
|
|
QStringList list;
|
|
list.append(QString("Name: %1").arg(m_vors[row]->m_name));
|
|
list.append(QString("Frequency: %1 MHz").arg(m_vors[row]->m_frequencykHz / 1000.0f, 0, 'f', 1));
|
|
if (m_vors[row]->m_channel != "")
|
|
list.append(QString("Channel: %1").arg(m_vors[row]->m_channel));
|
|
list.append(QString("Ident: %1 %2").arg(m_vors[row]->m_ident).arg(Morse::toSpacedUnicodeMorse(m_vors[row]->m_ident)));
|
|
list.append(QString("Range: %1 nm").arg(m_vors[row]->m_range));
|
|
if (m_vors[row]->m_alignedTrueNorth)
|
|
list.append(QString("Magnetic declination: Aligned to true North"));
|
|
else if (m_vors[row]->m_magneticDeclination != 0.0f)
|
|
list.append(QString("Magnetic declination: %1%2").arg(std::round(m_vors[row]->m_magneticDeclination)).arg(QChar(0x00b0)));
|
|
QString data = list.join("\n");
|
|
return QVariant::fromValue(data);
|
|
}
|
|
else if (role == VORModel::vorImageRole)
|
|
{
|
|
// Select an image to use for the VOR
|
|
return QVariant::fromValue(QString("/demodvor/map/%1.png").arg(m_vors[row]->m_type));
|
|
}
|
|
else if (role == VORModel::bubbleColourRole)
|
|
{
|
|
// Select a background colour for the text bubble next to the VOR
|
|
if (m_selected[row])
|
|
return QVariant::fromValue(QColor("lightgreen"));
|
|
else
|
|
return QVariant::fromValue(QColor("lightblue"));
|
|
}
|
|
else if (role == VORModel::vorRadialRole)
|
|
{
|
|
// Draw a radial line from centre of VOR outwards at the demodulated angle
|
|
if (m_radialsVisible && m_selected[row] && (m_vorGUIs[row] != nullptr) && (m_radials[row] != -1.0f))
|
|
{
|
|
QVariantList list;
|
|
|
|
list.push_back(m_vorGUIs[row]->m_coordinates[0]); // Centre of VOR
|
|
|
|
float endLat, endLong;
|
|
float bearing;
|
|
if (m_gui->m_settings.m_magDecAdjust && !m_vors[row]->m_alignedTrueNorth)
|
|
bearing = m_radials[row] - m_vors[row]->m_magneticDeclination;
|
|
else
|
|
bearing = m_radials[row];
|
|
calcRadialEndPoint(m_vors[row]->m_latitude, m_vors[row]->m_longitude, m_vors[row]->getRangeMetres(), bearing, endLat, endLong);
|
|
list.push_back(QVariant::fromValue(*new QGeoCoordinate(endLat, endLong, Units::feetToMetres(m_vors[row]->m_elevation))));
|
|
|
|
return list;
|
|
}
|
|
else
|
|
return QVariantList();
|
|
}
|
|
else if (role == VORModel::selectedRole)
|
|
return QVariant::fromValue(m_selected[row]);
|
|
return QVariant();
|
|
}
|
|
|
|
bool VORModel::setData(const QModelIndex &index, const QVariant& value, int role)
|
|
{
|
|
int row = index.row();
|
|
if ((row < 0) || (row >= m_vors.count()))
|
|
return false;
|
|
if (role == VORModel::selectedRole)
|
|
{
|
|
bool selected = value.toBool();
|
|
VORGUI *vorGUI;
|
|
if (selected == true)
|
|
{
|
|
vorGUI = new VORGUI(m_vors[row], m_gui);
|
|
m_vorGUIs[row] = vorGUI;
|
|
}
|
|
else
|
|
vorGUI = m_vorGUIs[row];
|
|
m_gui->selectVOR(vorGUI, selected);
|
|
m_selected[row] = selected;
|
|
emit dataChanged(index, index);
|
|
if (!selected)
|
|
{
|
|
delete vorGUI;
|
|
m_vorGUIs[row] = nullptr;
|
|
}
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Find intersection between first two selected radials
|
|
bool VORModel::findIntersection(float &lat, float &lon)
|
|
{
|
|
if (m_vors.count() > 2)
|
|
{
|
|
float lat1, lon1, bearing1, valid1 = false;
|
|
float lat2, lon2, bearing2, valid2 = false;
|
|
|
|
for (int i = 0; i < m_vors.count(); i++)
|
|
{
|
|
if (m_selected[i] && (m_radials[i] >= 0.0))
|
|
{
|
|
if (!valid1)
|
|
{
|
|
lat1 = m_vors[i]->m_latitude;
|
|
lon1 = m_vors[i]->m_longitude;
|
|
if (m_gui->m_settings.m_magDecAdjust && !m_vors[i]->m_alignedTrueNorth)
|
|
bearing1 = m_radials[i] - m_vors[i]->m_magneticDeclination;
|
|
else
|
|
bearing1 = m_radials[i];
|
|
valid1 = true;
|
|
}
|
|
else
|
|
{
|
|
lat2 = m_vors[i]->m_latitude;
|
|
lon2 = m_vors[i]->m_longitude;
|
|
if (m_gui->m_settings.m_magDecAdjust && !m_vors[i]->m_alignedTrueNorth)
|
|
bearing2 = m_radials[i] - m_vors[i]->m_magneticDeclination;
|
|
else
|
|
bearing2 = m_radials[i];
|
|
valid2 = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valid1 && valid2)
|
|
{
|
|
return calcIntersectionPoint(lat1, lon1, bearing1, lat2, lon2, bearing2, lat, lon);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void VORDemodGUI::resizeTable()
|
|
{
|
|
// Fill table with a row of dummy data that will size the columns nicely
|
|
// Trailing spaces are for sort arrow
|
|
QString morse("---- ---- ----");
|
|
int row = ui->vorData->rowCount();
|
|
ui->vorData->setRowCount(row + 1);
|
|
ui->vorData->setItem(row, VOR_COL_NAME, new QTableWidgetItem("White Sulphur Springs"));
|
|
ui->vorData->setItem(row, VOR_COL_FREQUENCY, new QTableWidgetItem("Freq (MHz) "));
|
|
ui->vorData->setItem(row, VOR_COL_OFFSET, new QTableWidgetItem("Offset (kHz) "));
|
|
ui->vorData->setItem(row, VOR_COL_IDENT, new QTableWidgetItem("Ident "));
|
|
ui->vorData->setItem(row, VOR_COL_MORSE, new QTableWidgetItem(Morse::toSpacedUnicode(morse)));
|
|
ui->vorData->setItem(row, VOR_COL_RADIAL, new QTableWidgetItem("Radial (o) "));
|
|
ui->vorData->setItem(row, VOR_COL_RX_IDENT, new QTableWidgetItem("RX Ident "));
|
|
ui->vorData->setItem(row, VOR_COL_RX_MORSE, new QTableWidgetItem(Morse::toSpacedUnicode(morse)));
|
|
ui->vorData->setItem(row, VOR_COL_VAR_MAG, new QTableWidgetItem("Var (dB) "));
|
|
ui->vorData->setItem(row, VOR_COL_REF_MAG, new QTableWidgetItem("Ref (dB) "));
|
|
ui->vorData->setItem(row, VOR_COL_MUTE, new QTableWidgetItem("Mute"));
|
|
ui->vorData->resizeColumnsToContents();
|
|
ui->vorData->removeRow(row);
|
|
}
|
|
|
|
// Columns in table reordered
|
|
void VORDemodGUI::vorData_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
|
|
{
|
|
(void) oldVisualIndex;
|
|
|
|
m_settings.m_columnIndexes[logicalIndex] = newVisualIndex;
|
|
}
|
|
|
|
// Column in table resized (when hidden size is 0)
|
|
void VORDemodGUI::vorData_sectionResized(int logicalIndex, int oldSize, int newSize)
|
|
{
|
|
(void) oldSize;
|
|
|
|
m_settings.m_columnSizes[logicalIndex] = newSize;
|
|
}
|
|
|
|
// Right click in table header - show column select menu
|
|
void VORDemodGUI::columnSelectMenu(QPoint pos)
|
|
{
|
|
menu->popup(ui->vorData->horizontalHeader()->viewport()->mapToGlobal(pos));
|
|
}
|
|
|
|
// Hide/show column when menu selected
|
|
void VORDemodGUI::columnSelectMenuChecked(bool checked)
|
|
{
|
|
(void) checked;
|
|
|
|
QAction* action = qobject_cast<QAction*>(sender());
|
|
if (action != nullptr)
|
|
{
|
|
int idx = action->data().toInt(nullptr);
|
|
ui->vorData->setColumnHidden(idx, !action->isChecked());
|
|
}
|
|
}
|
|
|
|
// Create column select menu item
|
|
QAction *VORDemodGUI::createCheckableItem(QString &text, int idx, bool checked)
|
|
{
|
|
QAction *action = new QAction(text, this);
|
|
action->setCheckable(true);
|
|
action->setChecked(checked);
|
|
action->setData(QVariant(idx));
|
|
connect(action, SIGNAL(triggered()), this, SLOT(columnSelectMenuChecked()));
|
|
return action;
|
|
}
|
|
|
|
// Called when a VOR is selected on the map
|
|
void VORDemodGUI::selectVOR(VORGUI *vorGUI, bool selected)
|
|
{
|
|
if (selected)
|
|
{
|
|
m_selectedVORs.insert(vorGUI->m_navAid->m_id, vorGUI);
|
|
ui->vorData->setSortingEnabled(false);
|
|
int row = ui->vorData->rowCount();
|
|
ui->vorData->setRowCount(row + 1);
|
|
ui->vorData->setItem(row, VOR_COL_NAME, vorGUI->m_nameItem);
|
|
ui->vorData->setItem(row, VOR_COL_FREQUENCY, vorGUI->m_frequencyItem);
|
|
ui->vorData->setItem(row, VOR_COL_OFFSET, vorGUI->m_offsetItem);
|
|
ui->vorData->setItem(row, VOR_COL_IDENT, vorGUI->m_identItem);
|
|
ui->vorData->setItem(row, VOR_COL_MORSE, vorGUI->m_morseItem);
|
|
ui->vorData->setItem(row, VOR_COL_RADIAL, vorGUI->m_radialItem);
|
|
ui->vorData->setItem(row, VOR_COL_RX_IDENT, vorGUI->m_rxIdentItem);
|
|
ui->vorData->setItem(row, VOR_COL_RX_MORSE, vorGUI->m_rxMorseItem);
|
|
ui->vorData->setItem(row, VOR_COL_VAR_MAG, vorGUI->m_varMagItem);
|
|
ui->vorData->setItem(row, VOR_COL_REF_MAG, vorGUI->m_refMagItem);
|
|
ui->vorData->setCellWidget(row, VOR_COL_MUTE, vorGUI->m_muteItem);
|
|
vorGUI->m_nameItem->setText(vorGUI->m_navAid->m_name);
|
|
vorGUI->m_identItem->setText(vorGUI->m_navAid->m_ident);
|
|
vorGUI->m_morseItem->setText(Morse::toSpacedUnicodeMorse(vorGUI->m_navAid->m_ident));
|
|
vorGUI->m_frequencyItem->setData(Qt::DisplayRole, vorGUI->m_navAid->m_frequencykHz / 1000.0);
|
|
ui->vorData->setSortingEnabled(true);
|
|
|
|
// Add to settings to create corresponding demodulator
|
|
VORDemodSubChannelSettings *subChannelSettings = new VORDemodSubChannelSettings();
|
|
subChannelSettings->m_id = vorGUI->m_navAid->m_id;
|
|
subChannelSettings->m_frequency = vorGUI->m_navAid->m_frequencykHz * 1000;
|
|
subChannelSettings->m_audioMute = false;
|
|
m_settings.m_subChannelSettings.insert(vorGUI->m_navAid->m_id, subChannelSettings);
|
|
applySettings();
|
|
}
|
|
else
|
|
{
|
|
m_selectedVORs.remove(vorGUI->m_navAid->m_id);
|
|
ui->vorData->removeRow(vorGUI->m_nameItem->row());
|
|
// Remove from settings to remove corresponding demodulator
|
|
VORDemodSubChannelSettings *subChannelSettings = m_settings.m_subChannelSettings.value(vorGUI->m_navAid->m_id);
|
|
m_settings.m_subChannelSettings.remove(vorGUI->m_navAid->m_id);
|
|
delete subChannelSettings;
|
|
applySettings();
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::updateVORs()
|
|
{
|
|
m_vorModel.removeAllVORs();
|
|
QHash<int, NavAid *>::iterator i = m_vors->begin();
|
|
AzEl azEl = m_azEl;
|
|
|
|
while (i != m_vors->end())
|
|
{
|
|
NavAid *vor = i.value();
|
|
|
|
// Calculate distance to VOR from My Position
|
|
azEl.setTarget(vor->m_latitude, vor->m_longitude, Units::feetToMetres(vor->m_elevation));
|
|
azEl.calculate();
|
|
|
|
// Only display VOR if in range
|
|
if (azEl.getDistance() <= 200000)
|
|
{
|
|
m_vorModel.addVOR(vor);
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
VORDemodGUI* VORDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
|
|
{
|
|
VORDemodGUI* gui = new VORDemodGUI(pluginAPI, deviceUISet, rxChannel);
|
|
return gui;
|
|
}
|
|
|
|
void VORDemodGUI::destroy()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
void VORDemodGUI::resetToDefaults()
|
|
{
|
|
m_settings.resetToDefaults();
|
|
displaySettings();
|
|
applySettings(true);
|
|
}
|
|
|
|
QByteArray VORDemodGUI::serialize() const
|
|
{
|
|
return m_settings.serialize();
|
|
}
|
|
|
|
bool VORDemodGUI::deserialize(const QByteArray& data)
|
|
{
|
|
if(m_settings.deserialize(data)) {
|
|
displaySettings();
|
|
applySettings(true);
|
|
return true;
|
|
} else {
|
|
resetToDefaults();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool VORDemodGUI::handleMessage(const Message& message)
|
|
{
|
|
if (VORDemod::MsgConfigureVORDemod::match(message))
|
|
{
|
|
qDebug("VORDemodGUI::handleMessage: VORDemod::MsgConfigureVORDemod");
|
|
const VORDemod::MsgConfigureVORDemod& cfg = (VORDemod::MsgConfigureVORDemod&) message;
|
|
m_settings = cfg.getSettings();
|
|
blockApplySettings(true);
|
|
displaySettings();
|
|
blockApplySettings(false);
|
|
return true;
|
|
}
|
|
else if (DSPSignalNotification::match(message))
|
|
{
|
|
DSPSignalNotification& notif = (DSPSignalNotification&) message;
|
|
m_basebandSampleRate = notif.getSampleRate();
|
|
return true;
|
|
}
|
|
else if (VORDemodReport::MsgReportRadial::match(message))
|
|
{
|
|
VORDemodReport::MsgReportRadial& report = (VORDemodReport::MsgReportRadial&) message;
|
|
int subChannelId = report.getSubChannelId();
|
|
|
|
VORGUI *vorGUI = m_selectedVORs.value(subChannelId);
|
|
|
|
// Display radial and signal magnitudes in table
|
|
|
|
Real varMagDB = std::round(20.0*std::log10(report.getVarMag()));
|
|
Real refMagDB = std::round(20.0*std::log10(report.getRefMag()));
|
|
|
|
bool validRadial = (refMagDB > m_settings.m_refThresholdDB) && (varMagDB > m_settings.m_varThresholdDB);
|
|
|
|
vorGUI->m_radialItem->setData(Qt::DisplayRole, std::round(report.getRadial()));
|
|
if (validRadial)
|
|
vorGUI->m_radialItem->setForeground(QBrush(Qt::white));
|
|
else
|
|
vorGUI->m_radialItem->setForeground(QBrush(Qt::red));
|
|
|
|
vorGUI->m_refMagItem->setData(Qt::DisplayRole, refMagDB);
|
|
if (refMagDB > m_settings.m_refThresholdDB)
|
|
vorGUI->m_refMagItem->setForeground(QBrush(Qt::white));
|
|
else
|
|
vorGUI->m_refMagItem->setForeground(QBrush(Qt::red));
|
|
|
|
vorGUI->m_varMagItem->setData(Qt::DisplayRole, varMagDB);
|
|
if (varMagDB > m_settings.m_varThresholdDB)
|
|
vorGUI->m_varMagItem->setForeground(QBrush(Qt::white));
|
|
else
|
|
vorGUI->m_varMagItem->setForeground(QBrush(Qt::red));
|
|
|
|
// Update radial on map
|
|
m_vorModel.setRadial(subChannelId, validRadial, report.getRadial());
|
|
|
|
return true;
|
|
}
|
|
else if (VORDemodReport::MsgReportFreqOffset::match(message))
|
|
{
|
|
VORDemodReport::MsgReportFreqOffset& report = (VORDemodReport::MsgReportFreqOffset&) message;
|
|
int subChannelId = report.getSubChannelId();
|
|
|
|
VORGUI *vorGUI = m_selectedVORs.value(subChannelId);
|
|
|
|
vorGUI->m_offsetItem->setData(Qt::DisplayRole, report.getFreqOffset() / 1000.0);
|
|
if (report.getOutOfBand())
|
|
{
|
|
vorGUI->m_offsetItem->setForeground(QBrush(Qt::red));
|
|
// Clear other fields as data is now invalid
|
|
vorGUI->m_radialItem->setText("");
|
|
vorGUI->m_refMagItem->setText("");
|
|
vorGUI->m_varMagItem->setText("");
|
|
m_vorModel.setRadial(subChannelId, false, -1.0f);
|
|
}
|
|
else
|
|
vorGUI->m_offsetItem->setForeground(QBrush(Qt::white));
|
|
}
|
|
else if (VORDemodReport::MsgReportIdent::match(message))
|
|
{
|
|
VORDemodReport::MsgReportIdent& report = (VORDemodReport::MsgReportIdent&) message;
|
|
int subChannelId = report.getSubChannelId();
|
|
|
|
VORGUI *vorGUI = m_selectedVORs.value(subChannelId);
|
|
|
|
QString ident = report.getIdent();
|
|
// Convert Morse to a string
|
|
QString identString = Morse::toString(ident);
|
|
// Idents should only be two or three characters, so filter anything else
|
|
// other than TEST which indicates a VOR is under maintainance (may also be TST)
|
|
if (((identString.size() >= 2) && (identString.size() <= 3)) || (identString == "TEST"))
|
|
{
|
|
vorGUI->m_rxIdentItem->setText(identString);
|
|
vorGUI->m_rxMorseItem->setText(Morse::toSpacedUnicode(ident));
|
|
if (vorGUI->m_navAid->m_ident == identString)
|
|
{
|
|
// Set colour to green if matching expected ident
|
|
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::green));
|
|
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::green));
|
|
}
|
|
else
|
|
{
|
|
// Set colour to green if not matching expected ident
|
|
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::red));
|
|
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::red));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set yellow to indicate we've filtered something (unless red)
|
|
if (vorGUI->m_rxIdentItem->foreground().color() != Qt::red)
|
|
{
|
|
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::yellow));
|
|
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::yellow));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void VORDemodGUI::handleInputMessages()
|
|
{
|
|
Message* message;
|
|
|
|
while ((message = getInputMessageQueue()->pop()) != 0)
|
|
{
|
|
if (handleMessage(*message))
|
|
{
|
|
delete message;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::channelMarkerChangedByCursor()
|
|
{
|
|
}
|
|
|
|
void VORDemodGUI::channelMarkerHighlightedByCursor()
|
|
{
|
|
setHighlighted(m_channelMarker.getHighlighted());
|
|
}
|
|
|
|
void VORDemodGUI::on_thresh_valueChanged(int value)
|
|
{
|
|
ui->threshText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
|
|
m_settings.m_identThreshold = value / 10.0;
|
|
applySettings();
|
|
}
|
|
|
|
void VORDemodGUI::on_volume_valueChanged(int value)
|
|
{
|
|
ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
|
|
m_settings.m_volume = value / 10.0;
|
|
applySettings();
|
|
}
|
|
|
|
void VORDemodGUI::on_squelch_valueChanged(int value)
|
|
{
|
|
ui->squelchText->setText(QString("%1 dB").arg(value));
|
|
m_settings.m_squelch = value;
|
|
applySettings();
|
|
}
|
|
|
|
void VORDemodGUI::on_audioMute_toggled(bool checked)
|
|
{
|
|
m_settings.m_audioMute = checked;
|
|
applySettings();
|
|
}
|
|
|
|
qint64 VORDemodGUI::fileAgeInDays(QString filename)
|
|
{
|
|
QFile file(filename);
|
|
if (file.exists())
|
|
{
|
|
QDateTime modified = file.fileTime(QFileDevice::FileModificationTime);
|
|
if (modified.isValid())
|
|
return modified.daysTo(QDateTime::currentDateTime());
|
|
else
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool VORDemodGUI::confirmDownload(QString filename)
|
|
{
|
|
qint64 age = fileAgeInDays(filename);
|
|
if ((age == -1) || (age > 100))
|
|
return true;
|
|
else
|
|
{
|
|
QMessageBox::StandardButton reply;
|
|
if (age == 0)
|
|
reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded today. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
|
|
else if (age == 1)
|
|
reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded yesterday. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
|
|
else
|
|
reply = QMessageBox::question(this, "Confirm download", QString("This file was last downloaded %1 days ago. Are you sure you wish to redownload this file?").arg(age), QMessageBox::Yes|QMessageBox::No);
|
|
return reply == QMessageBox::Yes;
|
|
}
|
|
}
|
|
|
|
QString VORDemodGUI::getDataDir()
|
|
{
|
|
// Get directory to store app data in
|
|
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
|
// First dir is writable
|
|
return locations[0];
|
|
}
|
|
|
|
QString VORDemodGUI::getOpenAIPVORDBFilename(int i)
|
|
{
|
|
if (countryCodes[i] != nullptr)
|
|
return getDataDir() + "/" + countryCodes[i] + "_nav.aip";
|
|
else
|
|
return "";
|
|
}
|
|
|
|
QString VORDemodGUI::getOpenAIPVORDBURL(int i)
|
|
{
|
|
if (countryCodes[i] != nullptr)
|
|
return QString(OPENAIP_NAVAIDS_URL).arg(countryCodes[i]);
|
|
else
|
|
return "";
|
|
}
|
|
|
|
QString VORDemodGUI::getVORDBFilename()
|
|
{
|
|
return getDataDir() + "/vorDatabase.csv";
|
|
}
|
|
|
|
void VORDemodGUI::updateDownloadProgress(qint64 bytesRead, qint64 totalBytes)
|
|
{
|
|
m_progressDialog->setMaximum(totalBytes);
|
|
m_progressDialog->setValue(bytesRead);
|
|
}
|
|
|
|
void VORDemodGUI::downloadFinished(const QString& filename, bool success)
|
|
{
|
|
if (success)
|
|
{
|
|
if (filename == getVORDBFilename())
|
|
{
|
|
m_vors = NavAid::readNavAidsDB(filename);
|
|
if (m_vors != nullptr)
|
|
updateVORs();
|
|
m_progressDialog->close();
|
|
m_progressDialog = nullptr;
|
|
}
|
|
else if (filename == getOpenAIPVORDBFilename(m_countryIndex))
|
|
{
|
|
m_countryIndex++;
|
|
if (countryCodes[m_countryIndex] != nullptr)
|
|
{
|
|
QString vorDBFile = getOpenAIPVORDBFilename(m_countryIndex);
|
|
QString urlString = getOpenAIPVORDBURL(m_countryIndex);
|
|
QUrl dbURL(urlString);
|
|
m_progressDialog->setLabelText(QString("Downloading %1.").arg(urlString));
|
|
m_progressDialog->setValue(m_countryIndex);
|
|
m_dlm.download(dbURL, vorDBFile);
|
|
}
|
|
else
|
|
{
|
|
readNavAids();
|
|
if (m_vors != nullptr)
|
|
updateVORs();
|
|
m_progressDialog->close();
|
|
m_progressDialog = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "VORDemodGUI::downloadFinished: Unexpected filename: " << filename;
|
|
m_progressDialog->close();
|
|
m_progressDialog = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "VORDemodGUI::downloadFinished: Failed: " << filename;
|
|
m_progressDialog->close();
|
|
m_progressDialog = nullptr;
|
|
QMessageBox::warning(this, "Download failed", QString("Failed to download %1").arg(filename));
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::on_getOurAirportsVORDB_clicked(bool checked)
|
|
{
|
|
(void) checked;
|
|
|
|
// Don't try to download while already in progress
|
|
if (m_progressDialog == nullptr)
|
|
{
|
|
QString vorDBFile = getVORDBFilename();
|
|
if (confirmDownload(vorDBFile))
|
|
{
|
|
// Download OurAirports navaid database to disk
|
|
QUrl dbURL(QString(OURAIRPORTS_NAVAIDS_URL));
|
|
m_progressDialog = new QProgressDialog(this);
|
|
m_progressDialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
m_progressDialog->setCancelButton(nullptr);
|
|
m_progressDialog->setMinimumDuration(500);
|
|
m_progressDialog->setLabelText(QString("Downloading %1.").arg(OURAIRPORTS_NAVAIDS_URL));
|
|
QNetworkReply *reply = m_dlm.download(dbURL, vorDBFile);
|
|
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::on_getOpenAIPVORDB_clicked(bool checked)
|
|
{
|
|
(void) checked;
|
|
|
|
// Don't try to download while already in progress
|
|
if (m_progressDialog == nullptr)
|
|
{
|
|
m_countryIndex = 0;
|
|
QString vorDBFile = getOpenAIPVORDBFilename(m_countryIndex);
|
|
if (confirmDownload(vorDBFile))
|
|
{
|
|
// Download OpenAIP XML to disk
|
|
QString urlString = getOpenAIPVORDBURL(m_countryIndex);
|
|
QUrl dbURL(urlString);
|
|
m_progressDialog = new QProgressDialog(this);
|
|
m_progressDialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
m_progressDialog->setCancelButton(nullptr);
|
|
m_progressDialog->setMinimumDuration(500);
|
|
m_progressDialog->setMaximum(sizeof(countryCodes)/sizeof(countryCodes[0]));
|
|
m_progressDialog->setValue(0);
|
|
m_progressDialog->setLabelText(QString("Downloading %1.").arg(urlString));
|
|
m_dlm.download(dbURL, vorDBFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::readNavAids()
|
|
{
|
|
m_vors = new QHash<int, NavAid *>();
|
|
for (int countryIndex = 0; countryCodes[countryIndex] != nullptr; countryIndex++)
|
|
{
|
|
QString vorDBFile = getOpenAIPVORDBFilename(countryIndex);
|
|
NavAid::readNavAidsXML(m_vors, vorDBFile);
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::on_magDecAdjust_clicked(bool checked)
|
|
{
|
|
m_settings.m_magDecAdjust = checked;
|
|
m_vorModel.allVORUpdated();
|
|
applySettings();
|
|
}
|
|
|
|
void VORDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
|
{
|
|
(void) widget;
|
|
(void) rollDown;
|
|
}
|
|
|
|
void VORDemodGUI::onMenuDialogCalled(const QPoint &p)
|
|
{
|
|
if (m_contextMenuType == ContextMenuChannelSettings)
|
|
{
|
|
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
|
|
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
|
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
|
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
|
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
|
|
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
|
|
dialog.move(p);
|
|
dialog.exec();
|
|
|
|
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
|
|
m_settings.m_title = m_channelMarker.getTitle();
|
|
m_settings.m_useReverseAPI = dialog.useReverseAPI();
|
|
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
|
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
|
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
|
|
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
|
|
|
|
setWindowTitle(m_settings.m_title);
|
|
setTitleColor(m_settings.m_rgbColor);
|
|
|
|
applySettings();
|
|
}
|
|
else if ((m_contextMenuType == ContextMenuStreamSettings) && (m_deviceUISet->m_deviceMIMOEngine))
|
|
{
|
|
DeviceStreamSelectionDialog dialog(this);
|
|
dialog.setNumberOfStreams(m_vorDemod->getNumberOfDeviceStreams());
|
|
dialog.setStreamIndex(m_settings.m_streamIndex);
|
|
dialog.move(p);
|
|
dialog.exec();
|
|
|
|
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
|
|
m_channelMarker.clearStreamIndexes();
|
|
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
|
|
displayStreamIndex();
|
|
applySettings();
|
|
}
|
|
|
|
resetContextMenuType();
|
|
}
|
|
|
|
VORDemodGUI::VORDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
|
|
ChannelGUI(parent),
|
|
ui(new Ui::VORDemodGUI),
|
|
m_pluginAPI(pluginAPI),
|
|
m_deviceUISet(deviceUISet),
|
|
m_channelMarker(this),
|
|
m_doApplySettings(true),
|
|
m_squelchOpen(false),
|
|
m_tickCount(0),
|
|
m_progressDialog(nullptr),
|
|
m_vorModel(this),
|
|
m_vors(nullptr)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
ui->map->rootContext()->setContextProperty("vorModel", &m_vorModel);
|
|
ui->map->setSource(QUrl(QStringLiteral("qrc:/demodvor/map/map.qml")));
|
|
|
|
m_muteIcon.addPixmap(QPixmap("://sound_off.png"), QIcon::Normal, QIcon::On);
|
|
m_muteIcon.addPixmap(QPixmap("://sound_on.png"), QIcon::Normal, QIcon::Off);
|
|
|
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
|
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
|
|
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
|
|
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &VORDemodGUI::downloadFinished);
|
|
|
|
m_vorDemod = reinterpret_cast<VORDemod*>(rxChannel);
|
|
m_vorDemod->setMessageQueueToGUI(getInputMessageQueue());
|
|
|
|
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
|
|
|
|
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute);
|
|
connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect()));
|
|
|
|
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
|
|
|
|
m_channelMarker.blockSignals(true);
|
|
m_channelMarker.setColor(Qt::yellow);
|
|
m_channelMarker.setBandwidth(2*48000);
|
|
m_channelMarker.setCenterFrequency(0);
|
|
m_channelMarker.setTitle("VOR Demodulator");
|
|
m_channelMarker.blockSignals(false);
|
|
m_channelMarker.setVisible(true); // activate signal on the last setting only
|
|
|
|
setTitleColor(m_channelMarker.getColor());
|
|
m_settings.setChannelMarker(&m_channelMarker);
|
|
|
|
m_deviceUISet->addChannelMarker(&m_channelMarker);
|
|
m_deviceUISet->addRollupWidget(this);
|
|
|
|
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
|
|
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
|
|
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
|
|
|
// 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);
|
|
|
|
// Centre map at My Position
|
|
QQuickItem *item = ui->map->rootObject();
|
|
QObject *object = item->findChild<QObject*>("map");
|
|
if(object != NULL)
|
|
{
|
|
QGeoCoordinate coords = object->property("center").value<QGeoCoordinate>();
|
|
coords.setLatitude(stationLatitude);
|
|
coords.setLongitude(stationLongitude);
|
|
object->setProperty("center", QVariant::fromValue(coords));
|
|
}
|
|
// Move antenna icon to My Position to start with
|
|
QObject *stationObject = item->findChild<QObject*>("station");
|
|
if(stationObject != NULL)
|
|
{
|
|
QGeoCoordinate coords = stationObject->property("coordinate").value<QGeoCoordinate>();
|
|
coords.setLatitude(stationLatitude);
|
|
coords.setLongitude(stationLongitude);
|
|
coords.setAltitude(stationAltitude);
|
|
stationObject->setProperty("coordinate", QVariant::fromValue(coords));
|
|
stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
|
|
}
|
|
|
|
// Read in VOR information if it exists
|
|
bool useOurAirports = false;
|
|
if (useOurAirports)
|
|
{
|
|
m_vors = NavAid::readNavAidsDB(getVORDBFilename());
|
|
ui->getOpenAIPVORDB->setVisible(false);
|
|
}
|
|
else
|
|
{
|
|
readNavAids();
|
|
ui->getOurAirportsVORDB->setVisible(false);
|
|
}
|
|
if (m_vors != nullptr)
|
|
updateVORs();
|
|
|
|
// Resize the table using dummy data
|
|
resizeTable();
|
|
// Allow user to reorder columns
|
|
ui->vorData->horizontalHeader()->setSectionsMovable(true);
|
|
// Allow user to sort table by clicking on headers
|
|
ui->vorData->setSortingEnabled(true);
|
|
// Add context menu to allow hiding/showing of columns
|
|
menu = new QMenu(ui->vorData);
|
|
for (int i = 0; i < ui->vorData->horizontalHeader()->count(); i++)
|
|
{
|
|
QString text = ui->vorData->horizontalHeaderItem(i)->text();
|
|
menu->addAction(createCheckableItem(text, i, true));
|
|
}
|
|
ui->vorData->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(ui->vorData->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint)));
|
|
// Get signals when columns change
|
|
connect(ui->vorData->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(vorData_sectionMoved(int, int, int)));
|
|
connect(ui->vorData->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(vorData_sectionResized(int, int, int)));
|
|
|
|
displaySettings();
|
|
applySettings(true);
|
|
}
|
|
|
|
VORDemodGUI::~VORDemodGUI()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
void VORDemodGUI::blockApplySettings(bool block)
|
|
{
|
|
m_doApplySettings = !block;
|
|
}
|
|
|
|
void VORDemodGUI::applySettings(bool force)
|
|
{
|
|
if (m_doApplySettings)
|
|
{
|
|
VORDemod::MsgConfigureVORDemod* message = VORDemod::MsgConfigureVORDemod::create( m_settings, force);
|
|
m_vorDemod->getInputMessageQueue()->push(message);
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::displaySettings()
|
|
{
|
|
m_channelMarker.blockSignals(true);
|
|
m_channelMarker.setCenterFrequency(0);
|
|
m_channelMarker.setBandwidth(m_basebandSampleRate > 0 ? m_basebandSampleRate : 2*48000);
|
|
m_channelMarker.setTitle(m_settings.m_title);
|
|
m_channelMarker.blockSignals(false);
|
|
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
|
|
|
|
setTitleColor(m_settings.m_rgbColor);
|
|
setWindowTitle(m_channelMarker.getTitle());
|
|
|
|
blockApplySettings(true);
|
|
|
|
ui->thresh->setValue(m_settings.m_identThreshold * 10.0);
|
|
ui->threshText->setText(QString("%1").arg(m_settings.m_identThreshold, 0, 'f', 1));
|
|
|
|
ui->volume->setValue(m_settings.m_volume * 10.0);
|
|
ui->volumeText->setText(QString("%1").arg(m_settings.m_volume, 0, 'f', 1));
|
|
|
|
ui->squelch->setValue(m_settings.m_squelch);
|
|
ui->squelchText->setText(QString("%1 dB").arg(m_settings.m_squelch));
|
|
|
|
ui->audioMute->setChecked(m_settings.m_audioMute);
|
|
|
|
displayStreamIndex();
|
|
|
|
// Order and size columns
|
|
QHeaderView *header = ui->vorData->horizontalHeader();
|
|
for (int i = 0; i < VORDEMOD_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->vorData->setColumnWidth(i, m_settings.m_columnSizes[i]);
|
|
header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]);
|
|
}
|
|
|
|
blockApplySettings(false);
|
|
}
|
|
|
|
void VORDemodGUI::displayStreamIndex()
|
|
{
|
|
if (m_deviceUISet->m_deviceMIMOEngine) {
|
|
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
|
|
} else {
|
|
setStreamIndicator("S"); // single channel indicator
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::leaveEvent(QEvent*)
|
|
{
|
|
m_channelMarker.setHighlighted(false);
|
|
}
|
|
|
|
void VORDemodGUI::enterEvent(QEvent*)
|
|
{
|
|
m_channelMarker.setHighlighted(true);
|
|
}
|
|
|
|
void VORDemodGUI::audioSelect()
|
|
{
|
|
qDebug("VORDemodGUI::audioSelect");
|
|
AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName);
|
|
audioSelect.exec();
|
|
|
|
if (audioSelect.m_selected)
|
|
{
|
|
m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName;
|
|
applySettings();
|
|
}
|
|
}
|
|
|
|
void VORDemodGUI::tick()
|
|
{
|
|
double magsqAvg, magsqPeak;
|
|
int nbMagsqSamples;
|
|
m_vorDemod->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(QString::number(powDbAvg, 'f', 1));
|
|
}
|
|
|
|
int audioSampleRate = m_vorDemod->getAudioSampleRate();
|
|
bool squelchOpen = m_vorDemod->getSquelchOpen();
|
|
|
|
if (squelchOpen != m_squelchOpen)
|
|
{
|
|
if (audioSampleRate < 0) {
|
|
ui->audioMute->setStyleSheet("QToolButton { background-color : red; }");
|
|
} else if (squelchOpen) {
|
|
ui->audioMute->setStyleSheet("QToolButton { background-color : green; }");
|
|
} else {
|
|
ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
|
|
}
|
|
|
|
m_squelchOpen = squelchOpen;
|
|
}
|
|
|
|
// Try to determine position, based on intersection of two radials
|
|
if (m_tickCount % 50)
|
|
{
|
|
float lat, lon;
|
|
|
|
if (m_vorModel.findIntersection(lat, lon))
|
|
{
|
|
// Move antenna icon to estimated position
|
|
QQuickItem *item = ui->map->rootObject();
|
|
QObject *stationObject = item->findChild<QObject*>("station");
|
|
if(stationObject != NULL)
|
|
{
|
|
QGeoCoordinate coords = stationObject->property("coordinate").value<QGeoCoordinate>();
|
|
coords.setLatitude(lat);
|
|
coords.setLongitude(lon);
|
|
stationObject->setProperty("coordinate", QVariant::fromValue(coords));
|
|
stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
|
|
}
|
|
}
|
|
}
|
|
|
|
m_tickCount++;
|
|
}
|