2020-11-24 12:31:16 +00:00
///////////////////////////////////////////////////////////////////////////////////
// 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 ));
2021-01-13 17:10:18 +00:00
intersectLat = Units :: radiansToDegrees ( lat3Rad );
intersectLon = Units :: radiansToDegrees ( lon3Rad );
2020-11-24 12:31:16 +00:00
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
2020-11-24 14:15:11 +00:00
QString morse ( "---- ---- ----" );
2020-11-24 12:31:16 +00:00
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 " ));
2020-11-24 14:15:11 +00:00
ui -> vorData -> setItem ( row , VOR_COL_MORSE , new QTableWidgetItem ( Morse :: toSpacedUnicode ( morse )));
ui -> vorData -> setItem ( row , VOR_COL_RADIAL , new QTableWidgetItem ( "Radial (o) " ));
2020-11-24 12:31:16 +00:00
ui -> vorData -> setItem ( row , VOR_COL_RX_IDENT , new QTableWidgetItem ( "RX Ident " ));
2020-11-24 14:15:11 +00:00
ui -> vorData -> setItem ( row , VOR_COL_RX_MORSE , new QTableWidgetItem ( Morse :: toSpacedUnicode ( morse )));
2020-11-24 12:31:16 +00:00
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 )
{
2020-11-24 14:15:11 +00:00
( void ) oldVisualIndex ;
2020-11-24 12:31:16 +00:00
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 )
{
2020-11-24 14:15:11 +00:00
( void ) oldSize ;
2020-11-24 12:31:16 +00:00
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 )
{
2020-11-24 14:15:11 +00:00
( void ) checked ;
2020-11-24 12:31:16 +00:00
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 );
2020-11-24 14:15:11 +00:00
m_dlm . download ( dbURL , vorDBFile );
2020-11-24 12:31:16 +00:00
}
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 )
{
2020-11-24 14:15:11 +00:00
( void ) checked ;
2020-11-24 12:31:16 +00:00
// 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 )
{
2020-11-24 14:15:11 +00:00
( void ) checked ;
2020-11-24 12:31:16 +00:00
// 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 ));
2020-11-24 14:15:11 +00:00
m_dlm . download ( dbURL , vorDBFile );
2020-11-24 12:31:16 +00:00
}
}
}
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 ++ ;
}