2024-04-02 16:13:01 +01:00
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023-2024 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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 <QDesktopServices>
#include <QAction>
#include <QClipboard>
#include <QMediaPlayer>
#include <QVideoWidget>
#include "feature/featureuiset.h"
#include "feature/featurewebapiutils.h"
#include "channel/channelwebapiutils.h"
#include "gui/crightclickenabler.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "gui/dialogpositioner.h"
#include "util/csv.h"
#include "util/astronomy.h"
2024-04-04 21:41:07 +01:00
#include "util/vlftransmitters.h"
2024-04-08 22:40:24 +02:00
#include "maincore.h"
2024-04-02 16:13:01 +01:00
#include "ui_sidgui.h"
#include "sid.h"
#include "sidgui.h"
#include "sidsettingsdialog.h"
2024-04-06 22:17:56 +01:00
#include "sidaddchannelsdialog.h"
2024-04-02 16:13:01 +01:00
2024-04-04 21:41:07 +01:00
#include "SWGMapItem.h"
2024-04-02 16:13:01 +01:00
SIDGUI * SIDGUI :: create ( PluginAPI * pluginAPI , FeatureUISet * featureUISet , Feature * feature )
{
SIDGUI * gui = new SIDGUI ( pluginAPI , featureUISet , feature );
return gui ;
}
void SIDGUI :: destroy ()
{
delete this ;
}
void SIDGUI :: resetToDefaults ()
{
m_settings . resetToDefaults ();
displaySettings ();
applyAllSettings ();
}
QByteArray SIDGUI :: serialize () const
{
return m_settings . serialize ();
}
bool SIDGUI :: deserialize ( const QByteArray & data )
{
if ( m_settings . deserialize ( data ))
{
m_feature -> setWorkspaceIndex ( m_settings . m_workspaceIndex );
displaySettings ();
applyAllSettings ();
return true ;
}
else
{
resetToDefaults ();
return false ;
}
}
bool SIDGUI :: handleMessage ( const Message & message )
{
if ( SIDMain :: MsgConfigureSID :: match ( message ))
{
qDebug ( "SIDGUI::handleMessage: SID::MsgConfigureSID" );
const SIDMain :: MsgConfigureSID & cfg = ( SIDMain :: MsgConfigureSID & ) message ;
if ( cfg . getForce ()) {
m_settings = cfg . getSettings ();
} else {
m_settings . applySettings ( cfg . getSettingsKeys (), cfg . getSettings ());
}
blockApplySettings ( true );
displaySettings ();
blockApplySettings ( false );
return true ;
}
else if ( SIDMain :: MsgMeasurement :: match ( message ))
{
2024-04-04 15:19:35 +01:00
// Measurements from SIDWorker
const SIDMain :: MsgMeasurement & measurementsMsg = ( SIDMain :: MsgMeasurement & ) message ;
QDateTime dt = measurementsMsg . getDateTime ();
const QStringList & ids = measurementsMsg . getIds ();
const QList < double >& measurements = measurementsMsg . getMeasurements ();
for ( int i = 0 ; i < ids . size (); i ++ ) {
addMeasurement ( ids [ i ], dt , measurements [ i ]);
}
2024-04-02 16:13:01 +01:00
return true ;
}
return false ;
}
void SIDGUI :: handleInputMessages ()
{
Message * message ;
while (( message = getInputMessageQueue () -> pop ()))
{
if ( handleMessage ( * message )) {
delete message ;
}
}
}
void SIDGUI :: onWidgetRolled ( QWidget * widget , bool rollDown )
{
( void ) widget ;
( void ) rollDown ;
RollupContents * rollupContents = getRollupContents ();
rollupContents -> saveState ( m_rollupState );
applySetting ( "rollupState" );
}
SIDGUI :: SIDGUI ( PluginAPI * pluginAPI , FeatureUISet * featureUISet , Feature * feature , QWidget * parent ) :
FeatureGUI ( parent ),
ui ( new Ui :: SIDGUI ),
m_pluginAPI ( pluginAPI ),
m_featureUISet ( featureUISet ),
m_doApplySettings ( true ),
m_lastFeatureState ( 0 ),
m_fileDialog ( nullptr , "Select CSV file" , "" , "*.csv" ),
m_chartXAxis ( nullptr ),
m_chartY1Axis ( nullptr ),
m_chartY2Axis ( nullptr ),
m_minMeasurement ( std :: numeric_limits < double >:: quiet_NaN ()),
m_maxMeasurement ( std :: numeric_limits < double >:: quiet_NaN ()),
m_xRayChartXAxis ( nullptr ),
m_xRayChartYAxis ( nullptr ),
m_goesXRay ( nullptr ),
m_solarDynamicsObservatory ( nullptr ),
m_player ( nullptr ),
m_grb ( nullptr ),
m_grbSeries ( nullptr ),
m_stix ( nullptr ),
m_stixSeries ( nullptr ),
2024-04-04 15:19:35 +01:00
m_availableFeatureHandler ({ "sdrangel.feature.map" }),
m_availableChannelHandler ({}, "RM" )
2024-04-02 16:13:01 +01:00
{
m_feature = feature ;
setAttribute ( Qt :: WA_DeleteOnClose , true );
m_helpURL = "plugins/feature/sid/readme.md" ;
RollupContents * rollupContents = getRollupContents ();
2025-01-17 13:33:53 +00:00
ui -> setupUi ( rollupContents );
2024-04-02 16:13:01 +01:00
rollupContents -> arrangeRollups ();
2025-01-17 13:33:53 +00:00
connect ( rollupContents , SIGNAL ( widgetRolled ( QWidget * , bool )), this , SLOT ( onWidgetRolled ( QWidget * , bool )));
2024-04-02 16:13:01 +01:00
m_sid = reinterpret_cast < SIDMain *> ( feature );
m_sid -> setMessageQueueToGUI ( & m_inputMessageQueue );
connect ( this , SIGNAL ( customContextMenuRequested ( const QPoint & )), this , SLOT ( onMenuDialogCalled ( const QPoint & )));
connect ( getInputMessageQueue (), SIGNAL ( messageEnqueued ()), this , SLOT ( handleInputMessages ()));
ui -> startDateTime -> blockSignals ( true );
ui -> endDateTime -> blockSignals ( true );
ui -> startDateTime -> setDateTime ( QDateTime ( QDate :: currentDate (), QTime ( 0 , 0 , 0 )));
ui -> endDateTime -> setDateTime ( QDateTime ( QDate :: currentDate (). addDays ( 1 ), QTime ( 0 , 0 , 0 )));
ui -> startDateTime -> blockSignals ( false );
ui -> endDateTime -> blockSignals ( false );
2024-07-10 22:59:13 +02:00
// Initialise chart
2024-04-02 16:13:01 +01:00
ui -> chart -> setRenderHint ( QPainter :: Antialiasing );
ui -> xRayChart -> setRenderHint ( QPainter :: Antialiasing );
connect ( & m_statusTimer , & QTimer :: timeout , this , & SIDGUI :: updateStatus );
m_statusTimer . start ( 250 );
connect ( & m_autosaveTimer , & QTimer :: timeout , this , & SIDGUI :: autosave );
m_settings . setRollupState ( & m_rollupState );
CRightClickEnabler * autoscaleXRightClickEnabler = new CRightClickEnabler ( ui -> autoscaleX );
connect ( autoscaleXRightClickEnabler , & CRightClickEnabler :: rightClick , this , & SIDGUI :: autoscaleXRightClicked );
CRightClickEnabler * autoscaleYRightClickEnabler = new CRightClickEnabler ( ui -> autoscaleY );
connect ( autoscaleYRightClickEnabler , & CRightClickEnabler :: rightClick , this , & SIDGUI :: autoscaleYRightClicked );
CRightClickEnabler * todayRightClickEnabler = new CRightClickEnabler ( ui -> today );
connect ( todayRightClickEnabler , & CRightClickEnabler :: rightClick , this , & SIDGUI :: todayRightClicked );
makeUIConnections (); // Enable connections before displaySettings, so autoscaling works
displaySettings ();
applyAllSettings ();
m_resizer . enableChildMouseTracking ();
2024-07-10 22:59:13 +02:00
// Initialisation for Solar Dynamics Observatory image/video display
2024-04-02 16:13:01 +01:00
ui -> sdoEnabled -> setChecked ( true );
ui -> sdoProgressBar -> setVisible ( false );
ui -> sdoImage -> setStyleSheet ( "background-color: black;" );
ui -> sdoVideo -> setStyleSheet ( "background-color: black;" );
m_solarDynamicsObservatory = SolarDynamicsObservatory :: create ();
if ( m_solarDynamicsObservatory )
{
m_player = new QMediaPlayer ();
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
connect ( m_player , qOverload < QMediaPlayer :: Error > ( & QMediaPlayer :: error ), this , & SIDGUI :: sdoVideoError );
connect ( m_player , & QMediaPlayer :: bufferStatusChanged , this , & SIDGUI :: sdoBufferStatusChanged );
#else
connect ( m_player , & QMediaPlayer :: errorOccurred , this , & SIDGUI :: sdoVideoError );
connect ( m_player , & QMediaPlayer :: bufferProgressChanged , this , & SIDGUI :: sdoBufferProgressChanged );
#endif
connect ( m_player , & QMediaPlayer :: mediaStatusChanged , this , & SIDGUI :: sdoVideoStatusChanged );
m_player -> setVideoOutput ( ui -> sdoVideo );
ui -> sdoData -> blockSignals ( true );
connect ( m_solarDynamicsObservatory , & SolarDynamicsObservatory :: imageUpdated , this , & SIDGUI :: sdoImageUpdated );
for ( const auto & name : SolarDynamicsObservatory :: getImageNames ()) {
ui -> sdoData -> addItem ( name );
}
ui -> sdoData -> blockSignals ( false );
ui -> sdoData -> setCurrentIndex ( 1 );
m_settings . m_sdoData = ui -> sdoData -> currentText ();
}
2024-07-10 22:59:13 +02:00
// Initialisation for GOES X-Ray data
2024-04-02 16:13:01 +01:00
m_goesXRay = GOESXRay :: create ();
if ( m_goesXRay )
{
connect ( m_goesXRay , & GOESXRay :: xRayDataUpdated , this , & SIDGUI :: xRayDataUpdated );
connect ( m_goesXRay , & GOESXRay :: protonDataUpdated , this , & SIDGUI :: protonDataUpdated );
m_goesXRay -> getDataPeriodically ();
}
// Get Gamma Ray Bursts
m_grb = GRB :: create ();
if ( m_grb )
{
connect ( m_grb , & GRB :: dataUpdated , this , & SIDGUI :: grbDataUpdated );
m_grb -> getDataPeriodically ();
}
// Get STIX Solar Flare data
m_stix = STIX :: create ();
if ( m_stix )
{
2025-01-17 13:33:53 +00:00
connect ( m_stix , & STIX :: dataUpdated , this , & SIDGUI :: stixDataUpdated );
m_stix -> getDataPeriodically ();
2024-04-02 16:13:01 +01:00
}
plotChart ();
QObject :: connect (
& m_availableFeatureHandler ,
& AvailableChannelOrFeatureHandler :: channelsOrFeaturesChanged ,
this ,
& SIDGUI :: featuresChanged
);
m_availableFeatureHandler . scanAvailableChannelsAndFeatures ();
2024-04-04 15:19:35 +01:00
QObject :: connect (
& m_availableChannelHandler ,
& AvailableChannelOrFeatureHandler :: channelsOrFeaturesChanged ,
this ,
& SIDGUI :: channelsChanged
);
m_availableChannelHandler . scanAvailableChannelsAndFeatures ();
2024-04-02 16:13:01 +01:00
QObject :: connect ( ui -> chartSplitter , & QSplitter :: splitterMoved , this , & SIDGUI :: chartSplitterMoved );
QObject :: connect ( ui -> sdoSplitter , & QSplitter :: splitterMoved , this , & SIDGUI :: sdoSplitterMoved );
}
SIDGUI ::~ SIDGUI ()
{
2024-05-19 22:51:26 +02:00
delete m_grb ;
delete m_stix ;
2024-04-02 16:13:01 +01:00
m_statusTimer . stop ();
2024-04-04 21:41:32 +01:00
clearFromMap ();
2024-04-02 16:13:01 +01:00
delete m_goesXRay ;
2024-05-22 23:32:13 +02:00
if ( m_solarDynamicsObservatory )
{
delete m_player ;
delete m_solarDynamicsObservatory ;
}
2024-04-02 16:13:01 +01:00
delete ui ;
}
void SIDGUI :: connectDataUpdates ()
{
if ( m_goesXRay )
{
connect ( m_goesXRay , & GOESXRay :: xRayDataUpdated , this , & SIDGUI :: xRayDataUpdated );
connect ( m_goesXRay , & GOESXRay :: protonDataUpdated , this , & SIDGUI :: protonDataUpdated );
}
}
void SIDGUI :: disconnectDataUpdates ()
{
if ( m_goesXRay )
{
disconnect ( m_goesXRay , & GOESXRay :: xRayDataUpdated , this , & SIDGUI :: xRayDataUpdated );
disconnect ( m_goesXRay , & GOESXRay :: protonDataUpdated , this , & SIDGUI :: protonDataUpdated );
}
}
void SIDGUI :: getData ()
{
if ( m_goesXRay ) {
m_goesXRay -> getData ();
}
}
void SIDGUI :: setWorkspaceIndex ( int index )
{
m_settings . m_workspaceIndex = index ;
m_feature -> setWorkspaceIndex ( index );
m_settingsKeys . append ( "workspaceIndex" );
}
void SIDGUI :: blockApplySettings ( bool block )
{
m_doApplySettings = ! block ;
}
void SIDGUI :: displaySettings ()
{
setTitleColor ( m_settings . m_rgbColor );
setWindowTitle ( m_settings . m_title );
setTitle ( m_settings . m_title );
blockApplySettings ( true );
ui -> samples -> setValue ( m_settings . m_samples );
ui -> separateCharts -> setChecked ( m_settings . m_separateCharts );
ui -> displayLegend -> setChecked ( m_settings . m_displayLegend );
ui -> plotXRayLongPrimary -> setChecked ( m_settings . m_plotXRayLongPrimary );
ui -> plotXRayLongSecondary -> setChecked ( m_settings . m_plotXRayLongSecondary );
ui -> plotXRayShortPrimary -> setChecked ( m_settings . m_plotXRayShortPrimary );
ui -> plotXRayShortSecondary -> setChecked ( m_settings . m_plotXRayShortSecondary );
ui -> plotGRB -> setChecked ( m_settings . m_plotGRB );
ui -> plotSTIX -> setChecked ( m_settings . m_plotSTIX );
ui -> plotProton -> setChecked ( m_settings . m_plotProton );
ui -> autoscaleX -> setChecked ( m_settings . m_autoscaleX );
ui -> autoscaleY -> setChecked ( m_settings . m_autoscaleY );
ui -> startDateTime -> clearMaximumDateTime ();
ui -> endDateTime -> clearMinimumDateTime ();
if ( m_settings . m_startDateTime . isValid ()) {
ui -> startDateTime -> setDateTime ( m_settings . m_startDateTime );
}
if ( m_settings . m_endDateTime . isValid ()) {
ui -> endDateTime -> setDateTime ( m_settings . m_endDateTime );
}
ui -> startDateTime -> setMaximumDateTime ( ui -> endDateTime -> dateTime ());
ui -> endDateTime -> setMinimumDateTime ( ui -> startDateTime -> dateTime ());
ui -> y1Min -> setValue ( m_settings . m_y1Min );
ui -> y1Max -> setValue ( m_settings . m_y1Max );
setAutoscaleX ();
setAutoscaleY ();
setXAxisRange ();
setY1AxisRange ();
setAutosaveTimer ();
ui -> sdoEnabled -> setChecked ( m_settings . m_sdoEnabled );
ui -> sdoVideoEnabled -> setChecked ( m_settings . m_sdoVideoEnabled );
ui -> sdoData -> setCurrentText ( m_settings . m_sdoData );
ui -> sdoNow -> setChecked ( m_settings . m_sdoNow );
ui -> sdoDateTime -> setEnabled ( ! m_settings . m_sdoNow );
ui -> mapLabel -> setEnabled ( ! m_settings . m_sdoNow );
ui -> map -> setEnabled ( ! m_settings . m_sdoNow );
ui -> sdoDateTime -> setDateTime ( m_settings . m_sdoDateTime );
ui -> map -> setCurrentText ( m_settings . m_map );
applySDO ();
applyDateTime ();
if ( m_settings . m_autoload ) {
readCSV ( m_settings . m_filename , true );
}
getRollupContents () -> restoreState ( m_rollupState );
if ( m_settings . m_chartSplitterSizes . size () > 0 ) {
ui -> chartSplitter -> setSizes ( m_settings . m_chartSplitterSizes );
}
if ( m_settings . m_sdoSplitterSizes . size () > 0 ) {
ui -> sdoSplitter -> setSizes ( m_settings . m_sdoSplitterSizes );
}
blockApplySettings ( false );
getRollupContents () -> arrangeRollups ();
}
void SIDGUI :: setAutosaveTimer ()
{
if ( m_settings . m_autosave ) {
m_autosaveTimer . start ( 1000 * 60 * m_settings . m_autosavePeriod );
} else {
m_autosaveTimer . stop ();
}
}
void SIDGUI :: onMenuDialogCalled ( const QPoint & p )
{
2024-08-25 23:36:46 +02:00
if ( m_contextMenuType == ContextMenuType :: ContextMenuChannelSettings )
2024-04-02 16:13:01 +01:00
{
BasicFeatureSettingsDialog dialog ( this );
dialog . setTitle ( m_settings . m_title );
dialog . setUseReverseAPI ( m_settings . m_useReverseAPI );
dialog . setReverseAPIAddress ( m_settings . m_reverseAPIAddress );
dialog . setReverseAPIPort ( m_settings . m_reverseAPIPort );
dialog . setReverseAPIFeatureSetIndex ( m_settings . m_reverseAPIFeatureSetIndex );
dialog . setReverseAPIFeatureIndex ( m_settings . m_reverseAPIFeatureIndex );
dialog . setDefaultTitle ( m_displayedName );
dialog . move ( p );
new DialogPositioner ( & dialog , false );
dialog . exec ();
m_settings . m_title = dialog . getTitle ();
m_settings . m_useReverseAPI = dialog . useReverseAPI ();
m_settings . m_reverseAPIAddress = dialog . getReverseAPIAddress ();
m_settings . m_reverseAPIPort = dialog . getReverseAPIPort ();
m_settings . m_reverseAPIFeatureSetIndex = dialog . getReverseAPIFeatureSetIndex ();
m_settings . m_reverseAPIFeatureIndex = dialog . getReverseAPIFeatureIndex ();
setTitle ( m_settings . m_title );
setTitleColor ( m_settings . m_rgbColor );
QStringList settingsKeys ({
"rgbColor" ,
"title" ,
"useReverseAPI" ,
"reverseAPIAddress" ,
"reverseAPIPort" ,
"reverseAPIDeviceIndex" ,
"reverseAPIChannelIndex"
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
applySettings ( m_settingsKeys );
}
resetContextMenuType ();
}
void SIDGUI :: applySetting ( const QString & settingsKey )
{
applySettings ({ settingsKey });
}
void SIDGUI :: applySettings ( const QStringList & settingsKeys , bool force )
{
m_settingsKeys . append ( settingsKeys );
if ( m_doApplySettings )
{
SIDMain :: MsgConfigureSID * message = SIDMain :: MsgConfigureSID :: create ( m_settings , m_settingsKeys , force );
m_sid -> getInputMessageQueue () -> push ( message );
m_settingsKeys . clear ();
}
m_settingsKeys . clear ();
}
void SIDGUI :: applyAllSettings ()
{
applySettings ( QStringList (), true );
}
void SIDGUI :: chartSplitterMoved ( int pos , int index )
{
2024-04-02 17:01:56 +01:00
( void ) pos ;
( void ) index ;
2024-04-02 16:13:01 +01:00
m_settings . m_chartSplitterSizes = ui -> chartSplitter -> sizes ();
applySetting ( "chartSplitterSizes" );
}
void SIDGUI :: sdoSplitterMoved ( int pos , int index )
{
2024-04-02 17:01:56 +01:00
( void ) pos ;
( void ) index ;
2024-04-02 16:13:01 +01:00
m_settings . m_sdoSplitterSizes = ui -> sdoSplitter -> sizes ();
applySetting ( "chartSplitterSizes" );
}
void SIDGUI :: on_samples_valueChanged ( int value )
{
m_settings . m_samples = value ;
applySetting ( "samples" );
plotChart ();
}
void SIDGUI :: on_separateCharts_toggled ( bool checked )
{
m_settings . m_separateCharts = checked ;
applySetting ( "separateCharts" );
plotChart ();
}
void SIDGUI :: on_displayLegend_toggled ( bool checked )
{
m_settings . m_displayLegend = checked ;
applySetting ( "displayLegend" );
plotChart ();
}
void SIDGUI :: on_plotXRayLongPrimary_toggled ( bool checked )
{
m_settings . m_plotXRayLongPrimary = checked ;
applySetting ( "plotXRayLongPrimary" );
plotChart ();
}
void SIDGUI :: on_plotXRayLongSecondary_toggled ( bool checked )
{
m_settings . m_plotXRayLongSecondary = checked ;
applySetting ( "plotXRayLongSecondary" );
plotChart ();
}
void SIDGUI :: on_plotXRayShortPrimary_toggled ( bool checked )
{
m_settings . m_plotXRayShortPrimary = checked ;
applySetting ( "plotXRayShortPrimary" );
plotChart ();
}
void SIDGUI :: on_plotXRayShortSecondary_toggled ( bool checked )
{
m_settings . m_plotXRayShortSecondary = checked ;
applySetting ( "plotXRayShortSecondary" );
plotChart ();
}
void SIDGUI :: on_plotGRB_toggled ( bool checked )
{
m_settings . m_plotGRB = checked ;
applySetting ( "plotGRB" );
plotChart ();
}
void SIDGUI :: on_plotSTIX_toggled ( bool checked )
{
m_settings . m_plotSTIX = checked ;
applySetting ( "plotSTIX" );
plotChart ();
}
void SIDGUI :: on_plotProton_toggled ( bool checked )
{
m_settings . m_plotProton = checked ;
applySetting ( "plotProton" );
plotChart ();
}
void SIDGUI :: on_startStop_toggled ( bool checked )
{
if ( m_doApplySettings )
{
SIDMain :: MsgStartStop * message = SIDMain :: MsgStartStop :: create ( checked );
m_sid -> getInputMessageQueue () -> push ( message );
}
}
void SIDGUI :: createGRBSeries ( QChart * chart , QDateTimeAxis * xAxis , QLogValueAxis * yAxis )
{
bool secondaryAxis = plotAnyXRay () || m_settings . m_plotSTIX ;
yAxis -> setLabelFormat ( "%.0e" );
yAxis -> setGridLineVisible ( ! secondaryAxis );
yAxis -> setTitleText ( "GRB Fluence (erg/cm<sup>2</sup>)" );
yAxis -> setTitleVisible ( m_settings . m_displayAxisTitles );
yAxis -> setVisible ( ! secondaryAxis || m_settings . m_displaySecondaryAxis );
if ( m_settings . m_plotGRB )
{
m_grbSeries = new QScatterSeries ();
m_grbSeries -> setName ( "GRB" );
m_grbSeries -> setColor ( m_settings . m_grbColor );
m_grbSeries -> setBorderColor ( m_settings . m_grbColor );
m_grbSeries -> setMarkerSize ( 8 );
for ( int i = 0 ; i < m_grbData . size (); i ++ )
{
float value = m_grbData [ i ]. m_fluence ;
if (( value <= 0.0f ) || std :: isnan ( value )) {
value = m_grbMin ; // <= 0 will result in series not being plotted, as log axis used
}
m_grbSeries -> append ( m_grbData [ i ]. m_dateTime . toMSecsSinceEpoch (), value );
}
yAxis -> setMin ( m_grbMin );
yAxis -> setMax ( m_grbMax );
chart -> addSeries ( m_grbSeries );
m_grbSeries -> attachAxis ( xAxis );
m_grbSeries -> attachAxis ( yAxis );
}
else
{
m_grbSeries = nullptr ;
}
}
void SIDGUI :: createFlareAxis ( QCategoryAxis * yAxis )
{
// Solar flare classification
yAxis -> setMin ( - 8 );
yAxis -> setMax ( - 3 );
yAxis -> setStartValue ( - 8 );
yAxis -> append ( "A" , - 7 );
yAxis -> append ( "B" , - 6 );
yAxis -> append ( "C" , - 5 );
yAxis -> append ( "M" , - 4 );
yAxis -> append ( "X" , - 3 );
yAxis -> setTitleText ( "Flare Class" );
yAxis -> setTitleVisible ( m_settings . m_displayAxisTitles );
yAxis -> setLineVisible ( m_settings . m_displaySecondaryAxis );
yAxis -> setGridLineVisible ( m_settings . m_separateCharts );
}
void SIDGUI :: createXRaySeries ( QChart * chart , QDateTimeAxis * xAxis , QCategoryAxis * yAxis )
{
createFlareAxis ( yAxis );
for ( int i = 0 ; i < 2 ; i ++ )
{
QString name = i == 0 ? "Primary" : "Secondary" ;
if ((( i == 0 ) && m_settings . m_plotXRayShortPrimary ) || (( i == 1 ) && m_settings . m_plotXRayShortSecondary ))
{
m_xrayShortMeasurements [ i ]. m_series = new QLineSeries ();
m_xrayShortMeasurements [ i ]. m_series -> setName ( QString ( "0.05-0.4nm X-Ray %1" ). arg ( name ));
m_xrayShortMeasurements [ i ]. m_series -> setColor ( m_settings . m_xrayShortColors [ i ]);
for ( int j = 0 ; j < m_xrayShortMeasurements [ i ]. m_measurements . size (); j ++ ) {
m_xrayShortMeasurements [ i ]. m_series -> append ( m_xrayShortMeasurements [ i ]. m_measurements [ j ]. m_dateTime . toMSecsSinceEpoch (), m_xrayShortMeasurements [ i ]. m_measurements [ j ]. m_measurement );
}
chart -> addSeries ( m_xrayShortMeasurements [ i ]. m_series );
m_xrayShortMeasurements [ i ]. m_series -> attachAxis ( xAxis );
m_xrayShortMeasurements [ i ]. m_series -> attachAxis ( yAxis );
}
else
{
m_xrayShortMeasurements [ i ]. m_series = nullptr ;
}
if ((( i == 0 ) && m_settings . m_plotXRayLongPrimary ) || (( i == 1 ) && m_settings . m_plotXRayLongSecondary ))
{
m_xrayLongMeasurements [ i ]. m_series = new QLineSeries ();
m_xrayLongMeasurements [ i ]. m_series -> setName ( QString ( "0.1-0.8nm X-Ray %1" ). arg ( name ));
m_xrayLongMeasurements [ i ]. m_series -> setColor ( m_settings . m_xrayLongColors [ i ]);
for ( int j = 0 ; j < m_xrayLongMeasurements [ i ]. m_measurements . size (); j ++ ) {
m_xrayLongMeasurements [ i ]. m_series -> append ( m_xrayLongMeasurements [ i ]. m_measurements [ j ]. m_dateTime . toMSecsSinceEpoch (), m_xrayLongMeasurements [ i ]. m_measurements [ j ]. m_measurement );
}
chart -> addSeries ( m_xrayLongMeasurements [ i ]. m_series );
m_xrayLongMeasurements [ i ]. m_series -> attachAxis ( xAxis );
m_xrayLongMeasurements [ i ]. m_series -> attachAxis ( yAxis );
}
else
{
m_xrayLongMeasurements [ i ]. m_series = nullptr ;
}
}
}
const QStringList SIDGUI :: m_protonEnergies = { "10 MeV" , "50 MeV" , "100 MeV" , "500 MeV" };
void SIDGUI :: createProtonSeries ( QChart * chart , QDateTimeAxis * xAxis , QLogValueAxis * yAxis )
{
bool secondaryAxis = plotAnyXRay () || m_settings . m_plotSTIX || m_settings . m_plotGRB ;
yAxis -> setLabelFormat ( "%.0e" );
yAxis -> setMin ( 0.01 );
yAxis -> setMax ( 1000.0 );
yAxis -> setGridLineVisible ( ! secondaryAxis );
yAxis -> setTitleText ( "Proton Flux (Particles / (cm<sup>2</sup> s sr))" );
yAxis -> setTitleVisible ( m_settings . m_displayAxisTitles );
yAxis -> setVisible ( ! secondaryAxis || m_settings . m_displaySecondaryAxis );
for ( int i = 0 ; i < 4 ; i += 2 ) // Only plot 10 and 100 MeV so graph isn't too cluttered
2025-01-17 13:33:53 +00:00
//for (int i = 0; i < 4; i++)
2024-04-02 16:13:01 +01:00
{
m_protonMeasurements [ i ]. m_series = new QLineSeries ();
m_protonMeasurements [ i ]. m_series -> setName ( QString ( "%1 Proton" ). arg ( SIDGUI :: m_protonEnergies [ i ]));
m_protonMeasurements [ i ]. m_series -> setColor ( m_settings . m_protonColors [ i ]);
for ( int j = 0 ; j < m_protonMeasurements [ i ]. m_measurements . size (); j ++ )
{
double value = m_protonMeasurements [ i ]. m_measurements [ j ]. m_measurement ;
if ( value >= 0.0 ) {
m_protonMeasurements [ i ]. m_series -> append ( m_protonMeasurements [ i ]. m_measurements [ j ]. m_dateTime . toMSecsSinceEpoch (), value );
}
}
chart -> addSeries ( m_protonMeasurements [ i ]. m_series );
m_protonMeasurements [ i ]. m_series -> attachAxis ( xAxis );
m_protonMeasurements [ i ]. m_series -> attachAxis ( yAxis );
}
}
void SIDGUI :: createSTIXSeries ( QChart * chart , QDateTimeAxis * xAxis , QCategoryAxis * yAxis )
{
createFlareAxis ( yAxis );
if ( m_settings . m_plotSTIX )
{
m_stixSeries = new QScatterSeries ();
m_stixSeries -> setName ( "STIX" );
m_stixSeries -> setColor ( m_settings . m_stixColor );
m_stixSeries -> setBorderColor ( m_settings . m_stixColor );
m_stixSeries -> setMarkerSize ( 5 );
for ( int i = 0 ; i < m_stixData . size (); i ++ )
{
double value = m_stixData [ i ]. m_flux ;
if ( value == 0.0 )
{
value = - 8 ;
}
else
{
value = log10 ( value );
}
m_stixSeries -> append ( m_stixData [ i ]. m_startDateTime . toMSecsSinceEpoch (), value );
}
chart -> addSeries ( m_stixSeries );
m_stixSeries -> attachAxis ( xAxis );
m_stixSeries -> attachAxis ( yAxis );
}
else
{
m_stixSeries = nullptr ;
}
}
void SIDGUI :: plotChart ()
{
QChart * oldChart = ui -> chart -> chart ();
QChart * chart ;
chart = new QChart ();
chart -> layout () -> setContentsMargins ( 0 , 0 , 0 , 0 );
chart -> setMargins ( QMargins ( 1 , 1 , 1 , 1 ));
chart -> setTheme ( QChart :: ChartThemeDark );
chart -> legend () -> setVisible ( m_settings . m_displayLegend );
chart -> legend () -> setAlignment ( m_settings . m_legendAlignment );
m_chartXAxis = new QDateTimeAxis ();
m_chartY1Axis = new QValueAxis ();
m_chartY2Axis = nullptr ;
m_chartY3Axis = nullptr ;
m_chartProtonAxis = nullptr ;
if ( ! m_settings . m_separateCharts )
{
// XRay flux
if ( plotAnyXRay () || m_settings . m_plotSTIX )
{
m_chartY2Axis = new QCategoryAxis ();
chart -> addAxis ( m_chartY2Axis , Qt :: AlignRight );
}
// GRB fluence
if ( m_settings . m_plotGRB )
{
m_chartY3Axis = new QLogValueAxis ();
chart -> addAxis ( m_chartY3Axis , Qt :: AlignRight );
}
// Proton flux
if ( m_settings . m_plotProton )
{
m_chartProtonAxis = new QLogValueAxis ();
chart -> addAxis ( m_chartProtonAxis , Qt :: AlignRight );
}
}
chart -> addAxis ( m_chartXAxis , Qt :: AlignBottom );
chart -> addAxis ( m_chartY1Axis , Qt :: AlignLeft );
m_chartY1Axis -> setTitleText ( "Power (dB)" );
m_chartY1Axis -> setTitleVisible ( m_settings . m_displayAxisTitles );
// Power measurements
for ( auto & measurement : m_channelMeasurements )
{
SIDSettings :: ChannelSettings * channelSettings = m_settings . getChannelSettings ( measurement . m_id );
if ( ! channelSettings ) {
qDebug () << "SIDGUI::plotChart: No settings for channel" << measurement . m_id ;
}
if ( channelSettings && channelSettings -> m_enabled )
{
QLineSeries * series = new QLineSeries ();
series -> setName ( channelSettings -> m_label );
series -> setColor ( channelSettings -> m_color );
measurement . newSeries ( series , m_settings . m_samples );
for ( int i = 0 ; i < measurement . m_measurements . size (); i ++ )
{
measurement . appendSeries ( measurement . m_measurements [ i ]. m_dateTime , measurement . m_measurements [ i ]. m_measurement );
updateMeasurementRange ( measurement . m_measurements [ i ]. m_measurement );
updateTimeRange ( measurement . m_measurements [ i ]. m_dateTime );
}
chart -> addSeries ( measurement . m_series );
measurement . m_series -> attachAxis ( m_chartXAxis );
measurement . m_series -> attachAxis ( m_chartY1Axis );
}
}
for ( int i = 0 ; i < 2 ; i ++ )
{
m_xrayShortMeasurements [ i ]. m_series = nullptr ;
m_xrayLongMeasurements [ i ]. m_series = nullptr ;
}
m_grbSeries = nullptr ;
m_stixSeries = nullptr ;
for ( int i = 0 ; i < 4 ; i ++ ) {
m_protonMeasurements [ i ]. m_series = nullptr ;
}
if ( ! m_settings . m_separateCharts )
{
// XRay
if ( plotAnyXRay ()) {
createXRaySeries ( chart , m_chartXAxis , m_chartY2Axis );
}
// GRB
if ( m_settings . m_plotGRB ) {
createGRBSeries ( chart , m_chartXAxis , m_chartY3Axis );
}
// STIX flares
if ( m_settings . m_plotSTIX ) {
createSTIXSeries ( chart , m_chartXAxis , m_chartY2Axis );
}
// Proton flux
if ( m_settings . m_plotProton ) {
createProtonSeries ( chart , m_chartXAxis , m_chartProtonAxis );
}
}
autoscaleX ();
autoscaleY ();
setXAxisRange ();
setY1AxisRange ();
ui -> chart -> setChart ( chart );
ui -> chart -> installEventFilter ( this );
delete oldChart ;
const auto markers = chart -> legend () -> markers ();
for ( QLegendMarker * marker : markers )
{
connect ( marker , & QLegendMarker :: clicked , this , & SIDGUI :: legendMarkerClicked );
}
for ( const auto series : chart -> series ())
{
QXYSeries * s = qobject_cast < QXYSeries *> ( series );
if ( s ) {
connect ( s , & QXYSeries :: clicked , this , & SIDGUI :: seriesClicked );
}
}
if ( m_settings . m_separateCharts )
{
ui -> xRayChart -> setVisible ( true );
plotXRayChart ();
}
else
{
ui -> xRayChart -> setVisible ( false );
}
}
bool SIDGUI :: plotAnyXRay () const
{
return m_settings . m_plotXRayLongPrimary || m_settings . m_plotXRayLongSecondary
|| m_settings . m_plotXRayShortPrimary || m_settings . m_plotXRayShortSecondary ;
}
void SIDGUI :: plotXRayChart ()
{
QChart * oldChart = ui -> xRayChart -> chart ();
QChart * chart ;
chart = new QChart ();
chart -> layout () -> setContentsMargins ( 0 , 0 , 0 , 0 );
chart -> setMargins ( QMargins ( 1 , 1 , 1 , 1 ));
chart -> setTheme ( QChart :: ChartThemeDark );
chart -> legend () -> setVisible ( m_settings . m_displayLegend );
chart -> legend () -> setAlignment ( m_settings . m_legendAlignment );
m_xRayChartXAxis = new QDateTimeAxis ();
chart -> addAxis ( m_xRayChartXAxis , Qt :: AlignBottom );
if ( plotAnyXRay () || m_settings . m_plotSTIX )
{
m_xRayChartYAxis = new QCategoryAxis ();
chart -> addAxis ( m_xRayChartYAxis , Qt :: AlignLeft );
}
if ( m_settings . m_plotGRB )
{
m_chartY3Axis = new QLogValueAxis ();
chart -> addAxis ( m_chartY3Axis , ( plotAnyXRay () || m_settings . m_plotSTIX ) ? Qt :: AlignRight : Qt :: AlignLeft );
}
if ( m_settings . m_plotProton )
{
m_chartProtonAxis = new QLogValueAxis ();
chart -> addAxis ( m_chartProtonAxis , ( plotAnyXRay () || m_settings . m_plotSTIX || m_settings . m_plotGRB ) ? Qt :: AlignRight : Qt :: AlignLeft );
}
// XRay
if ( plotAnyXRay ()) {
createXRaySeries ( chart , m_xRayChartXAxis , m_xRayChartYAxis );
}
// GRB
if ( m_settings . m_plotGRB ) {
createGRBSeries ( chart , m_xRayChartXAxis , m_chartY3Axis );
}
// STIX flares
if ( m_settings . m_plotSTIX ) {
createSTIXSeries ( chart , m_xRayChartXAxis , m_xRayChartYAxis );
}
// Proton flux
if ( m_settings . m_plotProton ) {
createProtonSeries ( chart , m_xRayChartXAxis , m_chartProtonAxis );
}
setXAxisRange ();
ui -> xRayChart -> setChart ( chart );
ui -> xRayChart -> installEventFilter ( this );
delete oldChart ;
const auto markers = chart -> legend () -> markers ();
for ( QLegendMarker * marker : markers ) {
connect ( marker , & QLegendMarker :: clicked , this , & SIDGUI :: legendMarkerClicked );
}
for ( const auto series : chart -> series ())
{
QXYSeries * s = qobject_cast < QXYSeries *> ( series );
if ( s ) {
connect ( s , & QXYSeries :: clicked , this , & SIDGUI :: seriesClicked );
}
}
if ( ! ( plotAnyXRay () || m_settings . m_plotGRB || m_settings . m_plotSTIX || m_settings . m_plotProton ))
{
ui -> xRayChart -> setVisible ( false ); // Hide empty chart
}
}
void SIDGUI :: legendMarkerClicked ()
{
QLegendMarker * marker = qobject_cast < QLegendMarker *> ( sender ());
marker -> series () -> setVisible ( ! marker -> series () -> isVisible ());
marker -> setVisible ( true );
// Dim the marker, if series is not visible
qreal alpha = 1.0 ;
if ( ! marker -> series () -> isVisible ()) {
alpha = 0.5 ;
}
QColor color ;
QBrush brush = marker -> labelBrush ();
color = brush . color ();
color . setAlphaF ( alpha );
brush . setColor ( color );
marker -> setLabelBrush ( brush );
brush = marker -> brush ();
color = brush . color ();
color . setAlphaF ( alpha );
brush . setColor ( color );
marker -> setBrush ( brush );
QPen pen = marker -> pen ();
color = pen . color ();
color . setAlphaF ( alpha );
pen . setColor ( color );
marker -> setPen ( pen );
}
void SIDGUI :: seriesClicked ( const QPointF & point )
{
QDateTime dt = QDateTime :: fromMSecsSinceEpoch ( point . x ());
ui -> sdoDateTime -> setDateTime ( dt );
}
static qreal distance ( const QPointF & a , const QPointF & b )
{
qreal dx = a . x () - b . x ();
qreal dy = a . y () - b . y ();
return qSqrt ( dx * dx + dy * dy );
}
qreal SIDGUI :: pixelDistance ( QChart * chart , QAbstractSeries * series , QPointF a , QPointF b )
{
a = chart -> mapToPosition ( a , series );
b = chart -> mapToPosition ( b , series );
return distance ( a , b );
}
void SIDGUI :: sendToSkyMap ( const AvailableChannelOrFeature & skymap , float ra , float dec )
{
QString target = QString ( "%1 %2" ). arg ( ra ). arg ( dec );
FeatureWebAPIUtils :: skyMapFind ( target , skymap . m_superIndex , skymap . m_index );
}
void SIDGUI :: showGRBContextMenu ( QContextMenuEvent * contextEvent , QChartView * chartView , int closestPoint )
{
QMenu * contextMenu = new QMenu ( chartView );
connect ( contextMenu , & QMenu :: aboutToHide , contextMenu , & QMenu :: deleteLater );
contextMenu -> addSection ( m_grbData [ closestPoint ]. m_name );
// Display GRB Fermi data
QString url = m_grbData [ closestPoint ]. getFermiURL ();
if ( ! url . isEmpty ())
{
QAction * fermiDataAction = new QAction ( "View Fermi data directory..." , contextMenu );
connect ( fermiDataAction , & QAction :: triggered , this , [ url ]() -> void {
QDesktopServices :: openUrl ( QUrl ( url ));
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
contextMenu -> addAction ( fermiDataAction );
QString plotURL = m_grbData [ closestPoint ]. getFermiPlotURL ();
QAction * fermiPlotAction = new QAction ( "View Fermi data plot..." , contextMenu );
connect ( fermiPlotAction , & QAction :: triggered , this , [ plotURL ]() -> void {
QDesktopServices :: openUrl ( QUrl ( plotURL ));
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
contextMenu -> addAction ( fermiPlotAction );
QString mapURL = m_grbData [ closestPoint ]. getFermiSkyMapURL ();
QAction * fermiMapDataAction = new QAction ( "View Fermi sky map..." , contextMenu );
connect ( fermiMapDataAction , & QAction :: triggered , this , [ mapURL ]() -> void {
QDesktopServices :: openUrl ( QUrl ( mapURL ));
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
contextMenu -> addAction ( fermiMapDataAction );
}
// Display Swift link
if ( ! m_grbData [ closestPoint ]. m_name . endsWith ( "*" ))
{
QAction * swiftDataAction = new QAction ( "View Swift data..." , contextMenu );
QString switftURL = m_grbData [ closestPoint ]. getSwiftURL ();
connect ( swiftDataAction , & QAction :: triggered , this , [ switftURL ]() -> void {
QDesktopServices :: openUrl ( QUrl ( switftURL ));
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
contextMenu -> addAction ( swiftDataAction );
}
// View GRB ra/dec in SkyMap
AvailableChannelOrFeatureHandler skymaps ({ "sdrangel.feature.skymap" });
skymaps . scanAvailableChannelsAndFeatures ();
if ( skymaps . getAvailableChannelOrFeatureList (). size () > 0 )
{
for ( const auto & skymap : skymaps . getAvailableChannelOrFeatureList ())
{
QString label = QString ( "View coords in %1..." ). arg ( skymap . getLongId ());
QAction * skyMapAction = new QAction ( label , contextMenu );
float ra = m_grbData [ closestPoint ]. m_ra ;
float dec = m_grbData [ closestPoint ]. m_dec ;
connect ( skyMapAction , & QAction :: triggered , this , [ this , skymap , ra , dec ]() -> void {
sendToSkyMap ( skymap , ra , dec );
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
contextMenu -> addAction ( skyMapAction );
}
}
else
{
QAction * skyMapAction = new QAction ( "View coords in SkyMap..." , contextMenu );
float ra = m_grbData [ closestPoint ]. m_ra ;
float dec = m_grbData [ closestPoint ]. m_dec ;
QString target = QString ( "%1 %2" ). arg ( ra ). arg ( dec );
connect ( skyMapAction , & QAction :: triggered , this , [ target ]() -> void {
FeatureWebAPIUtils :: openSkyMapAndFind ( target );
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
contextMenu -> addAction ( skyMapAction );
}
contextMenu -> popup ( chartView -> viewport () -> mapToGlobal ( contextEvent -> pos ()));
}
void SIDGUI :: showStixContextMenu ( QContextMenuEvent * contextEvent , QChartView * chartView , int closestPoint )
{
QMenu * contextMenu = new QMenu ( chartView );
connect ( contextMenu , & QMenu :: aboutToHide , contextMenu , & QMenu :: deleteLater );
contextMenu -> addSection ( m_stixData [ closestPoint ]. m_id );
// Display GRB Fermi data
QString lcURL = m_stixData [ closestPoint ]. getLightCurvesURL ();
QAction * lcAction = new QAction ( "View light curves..." , contextMenu );
connect ( lcAction , & QAction :: triggered , this , [ lcURL ]() -> void {
QDesktopServices :: openUrl ( QUrl ( lcURL ));
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
contextMenu -> addAction ( lcAction );
QString dataURL = m_stixData [ closestPoint ]. getDataURL ();
QAction * stixDataAction = new QAction ( "View STIX data..." , contextMenu );
connect ( stixDataAction , & QAction :: triggered , this , [ dataURL ]() -> void {
QDesktopServices :: openUrl ( QUrl ( dataURL ));
2025-01-17 13:33:53 +00:00
});
2024-04-02 16:13:01 +01:00
contextMenu -> addAction ( stixDataAction );
contextMenu -> popup ( chartView -> viewport () -> mapToGlobal ( contextEvent -> pos ()));
}
bool SIDGUI :: findClosestPoint ( QContextMenuEvent * contextEvent , QChart * chart , QScatterSeries * series , int & closestPoint )
{
QPointF point = chart -> mapToValue ( contextEvent -> pos (), series );
QDateTime dt = QDateTime :: fromMSecsSinceEpoch ( point . x ());
// Find nearest point - GRB/Stix data is ordered newest first
QVector < QPointF > points = series -> pointsVector ();
if ( points . size () > 0 )
{
qint64 startTime = m_settings . m_startDateTime . toMSecsSinceEpoch ();
qreal closestDistance = pixelDistance ( chart , series , point , points [ 0 ]);
closestPoint = 0 ;
for ( int i = 1 ; i < points . size (); i ++ )
{
qreal d = pixelDistance ( chart , series , point , points [ i ]);
if ( d < closestDistance )
{
closestDistance = d ;
closestPoint = i ;
}
if ( points [ i ]. x () < startTime ) {
break ;
}
}
return closestDistance <= series -> markerSize ();
}
else
{
return false ;
}
}
void SIDGUI :: showContextMenu ( QContextMenuEvent * contextEvent )
{
QChartView * chartView ;
if ( m_settings . m_separateCharts ) {
chartView = ui -> xRayChart ;
} else {
chartView = ui -> chart ;
}
if ( chartView )
{
int closestPoint ;
if ( m_grbSeries && findClosestPoint ( contextEvent , chartView -> chart (), m_grbSeries , closestPoint )) {
showGRBContextMenu ( contextEvent , chartView , closestPoint );
} else if ( m_stixSeries && findClosestPoint ( contextEvent , chartView -> chart (), m_stixSeries , closestPoint )) {
showStixContextMenu ( contextEvent , chartView , closestPoint );
}
}
}
bool SIDGUI :: eventFilter ( QObject * obj , QEvent * event )
{
if (( obj == ui -> chart ) || ( obj == ui -> xRayChart ))
{
if ( event -> type () == QEvent :: ContextMenu )
{
2024-04-05 17:30:39 +01:00
// Show context menu on chart for GRBs/Flares
2024-04-02 16:13:01 +01:00
QContextMenuEvent * contextEvent = static_cast < QContextMenuEvent *> ( event );
showContextMenu ( contextEvent );
contextEvent -> accept ();
return true ;
}
2024-04-05 17:30:39 +01:00
else if ( event -> type () == QEvent :: Wheel )
{
// Use wheel to zoom in / out of X axis or Y axis if shift held
QWheelEvent * wheelEvent = static_cast < QWheelEvent *> ( event );
int delta = wheelEvent -> angleDelta (). y (); // delta is typically 120 for one click of wheel
if ( wheelEvent -> modifiers () & Qt :: ShiftModifier )
{
double min = ui -> y1Min -> value ();
double max = ui -> y1Max -> value ();
double adj = ( max - min ) * 0.20 * delta / 120.0 ;
min += adj ;
max -= adj ;
ui -> y1Min -> setValue ( min );
ui -> y1Max -> setValue ( max );
}
else
{
QDateTime start = ui -> startDateTime -> dateTime ();
QDateTime end = ui -> endDateTime -> dateTime ();
qint64 startMS = start . toMSecsSinceEpoch ();
qint64 endMS = end . toMSecsSinceEpoch ();
qint64 diff = endMS - startMS ;
qint64 adj = diff * 0.20 * delta / 120.0 ;
endMS -= adj ;
startMS += adj ;
start = QDateTime :: fromMSecsSinceEpoch ( startMS );
end = QDateTime :: fromMSecsSinceEpoch ( endMS );
ui -> startDateTime -> setDateTime ( start );
ui -> endDateTime -> setDateTime ( end );
}
wheelEvent -> accept ();
return true ;
}
2024-04-02 16:13:01 +01:00
}
return FeatureGUI :: eventFilter ( obj , event );
}
void SIDGUI :: updateMeasurementRange ( double measurement )
{
if ( std :: isnan ( m_minMeasurement )) {
m_minMeasurement = measurement ;
} else {
m_minMeasurement = std :: min ( m_minMeasurement , measurement );
}
if ( std :: isnan ( m_maxMeasurement )) {
m_maxMeasurement = measurement ;
} else {
m_maxMeasurement = std :: max ( m_maxMeasurement , measurement );
}
}
void SIDGUI :: updateTimeRange ( QDateTime dateTime )
{
if ( ! m_minDateTime . isValid () || ( dateTime < m_minDateTime )) {
m_minDateTime = dateTime ;
}
if ( ! m_maxDateTime . isValid () || ( dateTime > m_maxDateTime )) {
m_maxDateTime = dateTime ;
}
}
void SIDGUI :: setXAxisRange ()
{
if ( m_chartXAxis ) {
m_chartXAxis -> setRange ( m_settings . m_startDateTime , m_settings . m_endDateTime );
}
if ( m_xRayChartXAxis ) {
m_xRayChartXAxis -> setRange ( m_settings . m_startDateTime , m_settings . m_endDateTime );
}
}
void SIDGUI :: setY1AxisRange ()
{
if ( m_chartY1Axis ) {
m_chartY1Axis -> setRange ( m_settings . m_y1Min , m_settings . m_y1Max );
}
}
void SIDGUI :: setButtonBackground ( QToolButton * button , bool checked )
{
if ( ! checked )
{
button -> setStyleSheet ( "" );
}
else
{
button -> setStyleSheet ( QString ( "QToolButton{ background-color: %1; }" )
. arg ( palette (). highlight (). color (). darker ( 150 ). name ()));
}
}
void SIDGUI :: setAutoscaleX ()
{
setButtonBackground ( ui -> autoscaleX , m_settings . m_autoscaleX );
}
void SIDGUI :: setAutoscaleY ()
{
setButtonBackground ( ui -> autoscaleY , m_settings . m_autoscaleY );
}
void SIDGUI :: on_autoscaleX_clicked ()
{
ui -> startDateTime -> clearMaximumDateTime ();
ui -> endDateTime -> clearMinimumDateTime ();
if ( m_minDateTime . isValid ())
{
ui -> startDateTime -> setDateTime ( m_minDateTime );
}
if ( m_maxDateTime . isValid ())
{
ui -> endDateTime -> setDateTime ( m_maxDateTime );
}
ui -> startDateTime -> setMaximumDateTime ( ui -> endDateTime -> dateTime ());
ui -> endDateTime -> setMinimumDateTime ( ui -> startDateTime -> dateTime ());
}
void SIDGUI :: on_autoscaleY_clicked ()
{
2024-04-05 16:35:43 +01:00
if ( ! std :: isnan ( m_minMeasurement ) && ! std :: isnan ( m_maxMeasurement ) && ( m_minMeasurement == m_maxMeasurement ))
{
// Graph doesn't display properly if min is the same as max
ui -> y1Min -> setValue ( m_minMeasurement * 0.99 );
ui -> y1Max -> setValue ( m_maxMeasurement * 1.01 );
2024-04-02 16:13:01 +01:00
}
2024-04-05 16:35:43 +01:00
else
{
if ( ! std :: isnan ( m_minMeasurement )) {
ui -> y1Min -> setValue ( m_minMeasurement );
}
if ( ! std :: isnan ( m_maxMeasurement )) {
ui -> y1Max -> setValue ( m_maxMeasurement );
}
2024-04-02 16:13:01 +01:00
}
}
void SIDGUI :: on_today_clicked ()
{
QDate today = QDate :: currentDate ();
QDateTime start = QDateTime ( today , QTime ( 0 , 0 ));
QDateTime end = QDateTime ( today . addDays ( 1 ), QTime ( 0 , 0 ));
ui -> startDateTime -> clearMaximumDateTime ();
ui -> endDateTime -> clearMinimumDateTime ();
ui -> startDateTime -> setDateTime ( start );
ui -> endDateTime -> setDateTime ( end );
ui -> startDateTime -> setMaximumDateTime ( ui -> endDateTime -> dateTime ());
ui -> endDateTime -> setMinimumDateTime ( ui -> startDateTime -> dateTime ());
}
void SIDGUI :: todayRightClicked ()
{
float stationLatitude = MainCore :: instance () -> getSettings (). getLatitude ();
float stationLongitude = MainCore :: instance () -> getSettings (). getLongitude ();
QDate today = QDate :: currentDate ();
QDateTime sunRise , sunSet ;
Astronomy :: sunrise ( today , stationLatitude , stationLongitude , sunRise , sunSet );
ui -> startDateTime -> clearMaximumDateTime ();
ui -> endDateTime -> clearMinimumDateTime ();
ui -> startDateTime -> setDateTime ( sunRise );
ui -> endDateTime -> setDateTime ( sunSet );
ui -> startDateTime -> setMaximumDateTime ( ui -> endDateTime -> dateTime ());
ui -> endDateTime -> setMinimumDateTime ( ui -> startDateTime -> dateTime ());
}
void SIDGUI :: on_prevDay_clicked ()
{
ui -> startDateTime -> clearMaximumDateTime ();
ui -> endDateTime -> clearMinimumDateTime ();
ui -> startDateTime -> setDateTime ( ui -> startDateTime -> dateTime (). addDays ( - 1 ));
ui -> endDateTime -> setDateTime ( ui -> endDateTime -> dateTime (). addDays ( - 1 ));
ui -> startDateTime -> setMaximumDateTime ( ui -> endDateTime -> dateTime ());
ui -> endDateTime -> setMinimumDateTime ( ui -> startDateTime -> dateTime ());
}
void SIDGUI :: on_nextDay_clicked ()
{
ui -> startDateTime -> clearMaximumDateTime ();
ui -> endDateTime -> clearMinimumDateTime ();
ui -> endDateTime -> setDateTime ( ui -> endDateTime -> dateTime (). addDays ( 1 ));
ui -> startDateTime -> setDateTime ( ui -> startDateTime -> dateTime (). addDays ( 1 ));
ui -> startDateTime -> setMaximumDateTime ( ui -> endDateTime -> dateTime ());
ui -> endDateTime -> setMinimumDateTime ( ui -> startDateTime -> dateTime ());
}
void SIDGUI :: autoscaleXRightClicked ()
{
m_settings . m_autoscaleX = ! m_settings . m_autoscaleX ;
applySetting ( "autoscaleX" );
setAutoscaleX ();
}
void SIDGUI :: autoscaleYRightClicked ()
{
m_settings . m_autoscaleY = ! m_settings . m_autoscaleY ;
applySetting ( "autoscaleY" );
setAutoscaleY ();
}
void SIDGUI :: on_startDateTime_dateTimeChanged ( QDateTime value )
{
m_settings . m_startDateTime = value ;
applySetting ( "startDateTime" );
setXAxisRange ();
ui -> endDateTime -> setMinimumDateTime ( value );
}
void SIDGUI :: on_endDateTime_dateTimeChanged ( QDateTime value )
{
m_settings . m_endDateTime = value ;
applySetting ( "endDateTime" );
setXAxisRange ();
ui -> startDateTime -> setMaximumDateTime ( value );
}
void SIDGUI :: on_y1Min_valueChanged ( double value )
{
m_settings . m_y1Min = ( float ) value ;
applySetting ( "y1Min" );
setY1AxisRange ();
}
void SIDGUI :: on_y1Max_valueChanged ( double value )
{
m_settings . m_y1Max = ( float ) value ;
applySetting ( "y1Max" );
setY1AxisRange ();
}
void SIDGUI :: clearMinMax ()
{
m_minDateTime = QDateTime ();
m_maxDateTime = QDateTime ();
m_minMeasurement = std :: numeric_limits < double >:: quiet_NaN ();
m_maxMeasurement = std :: numeric_limits < double >:: quiet_NaN ();
}
void SIDGUI :: clearAllData ()
{
m_channelMeasurements . clear ();
for ( int i = 0 ; i < 2 ; i ++ )
{
m_xrayShortMeasurements [ i ]. clear ();
m_xrayLongMeasurements [ i ]. clear ();
}
for ( int i = 0 ; i < 4 ; i ++ ) {
m_protonMeasurements [ i ]. clear ();
}
clearMinMax ();
}
void SIDGUI :: on_deleteAll_clicked ()
{
clearAllData ();
plotChart ();
getData ();
}
2024-04-06 22:17:56 +01:00
void SIDGUI :: on_addChannels_clicked ()
{
SIDAddChannelsDialog dialog ( & m_settings );
new DialogPositioner ( & dialog , true );
dialog . exec ();
}
2024-04-02 16:13:01 +01:00
void SIDGUI :: on_settings_clicked ()
{
SIDSettingsDialog dialog ( & m_settings );
2024-04-04 15:19:35 +01:00
QObject :: connect (
& dialog ,
& SIDSettingsDialog :: removeChannels ,
this ,
& SIDGUI :: removeChannels
);
2024-04-06 22:17:56 +01:00
new DialogPositioner ( & dialog , true );
2024-04-02 16:13:01 +01:00
if ( dialog . exec () == QDialog :: Accepted )
{
setAutosaveTimer ();
QStringList settingsKeys ;
settingsKeys . append ( "period" );
settingsKeys . append ( "autosave" );
settingsKeys . append ( "autoload" );
settingsKeys . append ( "filename" );
settingsKeys . append ( "autosavePeriod" );
settingsKeys . append ( "legendAlignment" );
settingsKeys . append ( "displayAxisTitles" );
settingsKeys . append ( "displayAxisLabels" );
settingsKeys . append ( "channelSettings" );
settingsKeys . append ( "xrayShortColors" );
settingsKeys . append ( "xrayLongColors" );
settingsKeys . append ( "protonColors" );
settingsKeys . append ( "grbColor" );
settingsKeys . append ( "stixColor" );
applySettings ( settingsKeys );
plotChart ();
}
}
void SIDGUI :: updateStatus ()
{
int state = m_sid -> getState ();
if ( m_lastFeatureState != state )
{
// We set checked state of start/stop button, in case it was changed via API
bool oldState ;
switch ( state )
{
2025-01-17 13:33:53 +00:00
case Feature :: StNotStarted :
ui -> startStop -> setStyleSheet ( "QToolButton { background:rgb(79,79,79); }" );
break ;
case Feature :: StIdle :
oldState = ui -> startStop -> blockSignals ( true );
ui -> startStop -> setChecked ( false );
ui -> startStop -> blockSignals ( oldState );
ui -> startStop -> setStyleSheet ( "QToolButton { background-color : blue; }" );
break ;
case Feature :: StRunning :
oldState = ui -> startStop -> blockSignals ( true );
ui -> startStop -> setChecked ( true );
ui -> startStop -> blockSignals ( oldState );
ui -> startStop -> setStyleSheet ( "QToolButton { background-color : green; }" );
break ;
case Feature :: StError :
ui -> startStop -> setStyleSheet ( "QToolButton { background-color : red; }" );
QMessageBox :: critical ( this , m_settings . m_title , m_sid -> getErrorMessage ());
break ;
default :
break ;
2024-04-02 16:13:01 +01:00
}
m_lastFeatureState = state ;
}
}
void SIDGUI :: makeUIConnections ()
{
QObject :: connect ( ui -> startStop , & ButtonSwitch :: toggled , this , & SIDGUI :: on_startStop_toggled );
QObject :: connect ( ui -> samples , QOverload < int >:: of ( & QSpinBox :: valueChanged ), this , & SIDGUI :: on_samples_valueChanged );
QObject :: connect ( ui -> separateCharts , & ButtonSwitch :: toggled , this , & SIDGUI :: on_separateCharts_toggled );
QObject :: connect ( ui -> displayLegend , & ButtonSwitch :: toggled , this , & SIDGUI :: on_displayLegend_toggled );
QObject :: connect ( ui -> plotXRayLongPrimary , & ButtonSwitch :: toggled , this , & SIDGUI :: on_plotXRayLongPrimary_toggled );
QObject :: connect ( ui -> plotXRayLongSecondary , & ButtonSwitch :: toggled , this , & SIDGUI :: on_plotXRayLongSecondary_toggled );
QObject :: connect ( ui -> plotXRayShortPrimary , & ButtonSwitch :: toggled , this , & SIDGUI :: on_plotXRayShortPrimary_toggled );
QObject :: connect ( ui -> plotXRayShortSecondary , & ButtonSwitch :: toggled , this , & SIDGUI :: on_plotXRayShortSecondary_toggled );
QObject :: connect ( ui -> plotGRB , & ButtonSwitch :: toggled , this , & SIDGUI :: on_plotGRB_toggled );
QObject :: connect ( ui -> plotSTIX , & ButtonSwitch :: toggled , this , & SIDGUI :: on_plotSTIX_toggled );
QObject :: connect ( ui -> plotProton , & ButtonSwitch :: toggled , this , & SIDGUI :: on_plotProton_toggled );
QObject :: connect ( ui -> sdoEnabled , & ButtonSwitch :: toggled , this , & SIDGUI :: on_sdoEnabled_toggled );
QObject :: connect ( ui -> sdoVideoEnabled , & ButtonSwitch :: toggled , this , & SIDGUI :: on_sdoVideoEnabled_toggled );
QObject :: connect ( ui -> sdoData , qOverload < int > ( & QComboBox :: currentIndexChanged ), this , & SIDGUI :: on_sdoData_currentIndexChanged );
QObject :: connect ( ui -> sdoNow , & ButtonSwitch :: toggled , this , & SIDGUI :: on_sdoNow_toggled );
QObject :: connect ( ui -> sdoDateTime , & WrappingDateTimeEdit :: dateTimeChanged , this , & SIDGUI :: on_sdoDateTime_dateTimeChanged );
QObject :: connect ( ui -> showSats , & QToolButton :: clicked , this , & SIDGUI :: on_showSats_clicked );
QObject :: connect ( ui -> map , & QComboBox :: currentTextChanged , this , & SIDGUI :: on_map_currentTextChanged );
2024-04-04 21:41:07 +01:00
QObject :: connect ( ui -> showPaths , & QToolButton :: clicked , this , & SIDGUI :: on_showPaths_clicked );
2024-04-02 16:13:01 +01:00
QObject :: connect ( ui -> autoscaleX , & QPushButton :: clicked , this , & SIDGUI :: on_autoscaleX_clicked );
QObject :: connect ( ui -> autoscaleY , & QPushButton :: clicked , this , & SIDGUI :: on_autoscaleY_clicked );
QObject :: connect ( ui -> today , & QPushButton :: clicked , this , & SIDGUI :: on_today_clicked );
QObject :: connect ( ui -> prevDay , & QPushButton :: clicked , this , & SIDGUI :: on_prevDay_clicked );
QObject :: connect ( ui -> nextDay , & QPushButton :: clicked , this , & SIDGUI :: on_nextDay_clicked );
QObject :: connect ( ui -> startDateTime , & WrappingDateTimeEdit :: dateTimeChanged , this , & SIDGUI :: on_startDateTime_dateTimeChanged );
QObject :: connect ( ui -> endDateTime , & WrappingDateTimeEdit :: dateTimeChanged , this , & SIDGUI :: on_endDateTime_dateTimeChanged );
QObject :: connect ( ui -> y1Min , QOverload < double >:: of ( & QDoubleSpinBox :: valueChanged ), this , & SIDGUI :: on_y1Min_valueChanged );
QObject :: connect ( ui -> y1Max , QOverload < double >:: of ( & QDoubleSpinBox :: valueChanged ), this , & SIDGUI :: on_y1Max_valueChanged );
QObject :: connect ( ui -> deleteAll , & QToolButton :: clicked , this , & SIDGUI :: on_deleteAll_clicked );
QObject :: connect ( ui -> saveData , & QToolButton :: clicked , this , & SIDGUI :: on_saveData_clicked );
QObject :: connect ( ui -> loadData , & QToolButton :: clicked , this , & SIDGUI :: on_loadData_clicked );
QObject :: connect ( ui -> saveChartImage , & QToolButton :: clicked , this , & SIDGUI :: on_saveChartImage_clicked );
2024-04-06 22:17:56 +01:00
QObject :: connect ( ui -> addChannels , & QToolButton :: clicked , this , & SIDGUI :: on_addChannels_clicked );
2024-04-02 16:13:01 +01:00
QObject :: connect ( ui -> settings , & QToolButton :: clicked , this , & SIDGUI :: on_settings_clicked );
}
SIDGUI :: ChannelMeasurement & SIDGUI :: addMeasurements ( const QString & id )
{
ChannelMeasurement measurements = ChannelMeasurement ( id , m_settings . m_samples );
m_channelMeasurements . append ( measurements );
return m_channelMeasurements . last ();
}
SIDGUI :: ChannelMeasurement & SIDGUI :: getMeasurements ( const QString & id )
{
for ( int i = 0 ; i < m_channelMeasurements . size (); i ++ )
{
if ( m_channelMeasurements [ i ]. m_id == id )
{
return m_channelMeasurements [ i ];
}
}
return addMeasurements ( id );
}
void SIDGUI :: addMeasurement ( const QString & id , QDateTime dateTime , double measurement )
{
ChannelMeasurement & measurements = getMeasurements ( id );
measurements . append ( dateTime , measurement );
if ( m_chartXAxis )
{
if ( measurements . m_series )
{
updateMeasurementRange ( measurement );
updateTimeRange ( dateTime );
autoscaleX ();
autoscaleY ();
}
else
{
qDebug () << "addMeasurement - measurement has no series calling plotChart" ;
plotChart ();
}
}
else
{
qDebug () << "addMeasurement with no m_chartXAxis - calling plotChart" ;
plotChart ();
}
}
void SIDGUI :: autoscaleX ()
{
if ( m_settings . m_autoscaleX )
{
if ( m_maxDateTime . isValid () && ( ! m_settings . m_endDateTime . isValid () || ( m_maxDateTime > m_settings . m_endDateTime ))) {
ui -> endDateTime -> setDateTime ( m_maxDateTime );
}
if ( m_minDateTime . isValid () && ( ! m_settings . m_startDateTime . isValid () || ( m_minDateTime < m_settings . m_startDateTime ))) {
ui -> startDateTime -> setDateTime ( m_minDateTime );
}
}
}
void SIDGUI :: autoscaleY ()
{
if ( m_settings . m_autoscaleY )
{
2024-04-05 16:35:43 +01:00
if ( ! std :: isnan ( m_minMeasurement ) && ! std :: isnan ( m_maxMeasurement ) && ( m_minMeasurement == m_maxMeasurement ))
{
// Graph doesn't display properly if min is the same as max
ui -> y1Min -> setValue ( m_minMeasurement * 0.99 );
ui -> y1Max -> setValue ( m_maxMeasurement * 1.01 );
2024-04-02 16:13:01 +01:00
}
2024-04-05 16:35:43 +01:00
else
{
if ( ! std :: isnan ( m_minMeasurement ) && ( m_minMeasurement != m_settings . m_y1Min )) {
ui -> y1Min -> setValue ( m_minMeasurement );
}
if ( ! std :: isnan ( m_maxMeasurement ) && ( m_maxMeasurement != m_settings . m_y1Max )) {
ui -> y1Max -> setValue ( m_maxMeasurement );
}
2024-04-02 16:13:01 +01:00
}
}
}
void SIDGUI :: xRayDataUpdated ( const QList < GOESXRay :: XRayData >& data , bool primary )
{
// Data is at 1-minute intervals, for last 6 hours, so we want to merge with data with already have
// Assuems oldest data is first in the array
QDateTime start ;
int idx = primary ? 0 : 1 ;
if ( m_xrayShortMeasurements [ idx ]. m_measurements . size () > 0 ) {
start = m_xrayShortMeasurements [ idx ]. m_measurements . last (). m_dateTime ;
}
for ( const auto & measurement : data )
{
if ( ! start . isValid () || ( measurement . m_dateTime > start ))
{
2024-04-02 17:01:56 +01:00
ChannelMeasurement * measurements ;
2024-04-02 16:13:01 +01:00
switch ( measurement . m_band )
{
case GOESXRay :: XRayData :: SHORT :
measurements = & m_xrayShortMeasurements [ idx ];
break ;
case GOESXRay :: XRayData :: LONG :
measurements = & m_xrayLongMeasurements [ idx ];
break ;
2024-04-02 17:01:56 +01:00
default :
measurements = nullptr ;
break ;
2024-04-02 16:13:01 +01:00
}
// Ignore flux measurements of 0, as log10(0) is -Inf
if ( measurements && ( measurement . m_flux != 0.0 ))
{
double logFlux = log10 ( measurement . m_flux );
measurements -> append ( measurement . m_dateTime , logFlux );
}
}
}
plotChart ();
}
void SIDGUI :: protonDataUpdated ( const QList < GOESXRay :: ProtonData >& data , bool primary )
{
2024-04-02 17:01:56 +01:00
( void ) primary ;
2024-04-02 16:13:01 +01:00
QDateTime start ;
2024-04-02 17:01:56 +01:00
2024-04-02 16:13:01 +01:00
if ( m_protonMeasurements [ 0 ]. m_measurements . size () > 0 ) {
start = m_protonMeasurements [ 0 ]. m_measurements . last (). m_dateTime ;
}
for ( const auto & measurement : data )
{
if ( ! start . isValid () || ( measurement . m_dateTime > start ))
{
ChannelMeasurement * measurements = nullptr ;
switch ( measurement . m_energy )
{
case 10 :
measurements = & m_protonMeasurements [ 0 ];
break ;
case 50 :
measurements = & m_protonMeasurements [ 1 ];
break ;
case 100 :
measurements = & m_protonMeasurements [ 2 ];
break ;
case 500 :
measurements = & m_protonMeasurements [ 3 ];
break ;
}
if ( measurements ) {
measurements -> append ( measurement . m_dateTime , measurement . m_flux );
}
}
}
plotChart ();
}
void SIDGUI :: stixDataUpdated ( const QList < STIX :: FlareData >& data )
{
m_stixData = data ;
plotChart ();
}
void SIDGUI :: grbDataUpdated ( const QList < GRB :: Data >& data )
{
m_grbData = data ;
// Calculate min/max of data
if ( m_grbData . size () > 0 )
{
m_grbMin = std :: numeric_limits < float >:: max ();
m_grbMax = std :: numeric_limits < float >:: min ();
for ( int i = 0 ; i < m_grbData . size (); i ++ )
{
if (( m_grbData [ i ]. m_fluence != 0.0f ) && ( m_grbData [ i ]. m_fluence != - 999.0f ))
{
m_grbMin = std :: min ( m_grbMin , m_grbData [ i ]. m_fluence );
m_grbMax = std :: max ( m_grbMax , m_grbData [ i ]. m_fluence );
}
}
}
plotChart ();
}
void SIDGUI :: sdoImageUpdated ( const QImage & image )
{
2024-04-02 17:01:56 +01:00
bool setSize = ui -> sdoImage -> pixmap ( Qt :: ReturnByValueConstant ()). isNull ();
2024-04-02 16:13:01 +01:00
QPixmap pixmap ;
pixmap . convertFromImage ( image );
ui -> sdoImage -> setPixmap ( pixmap );
if ( setSize )
{
QList < int > sizes = ui -> sdoSplitter -> sizes ();
if ( ! (( sizes [ 0 ] == 0 ) && ( sizes [ 1 ] == 0 )))
{
sizes [ 1 ] = std :: max ( sizes [ 1 ], 256 ); // Default size can be a bit small
ui -> sdoSplitter -> setSizes ( sizes );
}
}
}
void SIDGUI :: on_sdoEnabled_toggled ( bool checked )
{
m_settings . m_sdoEnabled = checked ;
ui -> sdoData -> setVisible ( checked );
ui -> sdoVideoEnabled -> setVisible ( checked );
ui -> sdoContainer -> setVisible ( checked );
ui -> sdoNow -> setVisible ( checked );
ui -> sdoDateTime -> setVisible ( checked );
applySetting ( "sdoEnabled" );
applySDO ();
}
void SIDGUI :: on_sdoVideoEnabled_toggled ( bool checked )
{
m_settings . m_sdoVideoEnabled = checked ;
applySetting ( "sdoVideoEnabled" );
QString currentText = ui -> sdoData -> currentText ();
ui -> sdoData -> blockSignals ( true );
ui -> sdoData -> clear ();
if ( checked )
{
for ( const auto & name : SolarDynamicsObservatory :: getVideoNames ()) {
ui -> sdoData -> addItem ( name );
}
}
else
{
for ( const auto & name : SolarDynamicsObservatory :: getImageNames ()) {
ui -> sdoData -> addItem ( name );
}
}
ui -> sdoData -> blockSignals ( false );
int idx = ui -> sdoData -> findText ( currentText );
if ( idx != - 1 ) {
ui -> sdoData -> setCurrentIndex ( idx );
} else {
ui -> sdoData -> setCurrentIndex ( 0 );
}
applySDO ();
}
void SIDGUI :: on_sdoNow_toggled ( bool checked )
{
m_settings . m_sdoNow = checked ;
applySetting ( "sdoNow" );
ui -> sdoDateTime -> setEnabled ( ! m_settings . m_sdoNow );
ui -> mapLabel -> setEnabled ( ! m_settings . m_sdoNow );
ui -> map -> setEnabled ( ! m_settings . m_sdoNow );
applySDO ();
applyDateTime ();
}
void SIDGUI :: on_sdoData_currentIndexChanged ( int index )
{
( void ) index ;
m_settings . m_sdoData = ui -> sdoData -> currentText ();
applySetting ( "sdoData" );
applySDO ();
}
void SIDGUI :: on_sdoDateTime_dateTimeChanged ( QDateTime value )
{
m_settings . m_sdoDateTime = value ;
applySetting ( "sdoDateTime" );
if ( ! m_settings . m_sdoNow )
{
applySDO ();
applyDateTime ();
}
}
void SIDGUI :: applySDO ()
{
if ( m_solarDynamicsObservatory )
{
ui -> sdoImage -> setVisible ( ! m_settings . m_sdoVideoEnabled );
ui -> sdoVideo -> setVisible ( m_settings . m_sdoVideoEnabled );
if ( m_player ) {
m_player -> stop ();
}
if ( m_settings . m_sdoVideoEnabled )
{
QString videoURL = SolarDynamicsObservatory :: getVideoURL ( m_settings . m_sdoData );
if ( ! videoURL . isEmpty () && m_player )
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
m_player -> setMedia ( QUrl ( videoURL ));
#else
m_player -> setSource ( QUrl ( videoURL ));
#endif
m_player -> play ();
}
// Stop image updates
m_solarDynamicsObservatory -> getImagePeriodically ( m_settings . m_sdoData , 512 , 0 );
}
else
{
if ( m_settings . m_sdoNow ) {
m_solarDynamicsObservatory -> getImagePeriodically ( m_settings . m_sdoData );
} else {
m_solarDynamicsObservatory -> getImage ( m_settings . m_sdoData , m_settings . m_sdoDateTime );
}
}
}
}
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
// This doesn't seem to get called on Qt5 on Windows
void SIDGUI :: sdoBufferStatusChanged ( int percentFilled )
{
ui -> sdoProgressBar -> setValue ( percentFilled );
}
#else
void SIDGUI :: sdoBufferProgressChanged ( float filled )
{
ui -> sdoProgressBar -> setValue (( int ) std :: round ( filled * 100.0f ));
}
#endif
void SIDGUI :: sdoVideoError ( QMediaPlayer :: Error error )
{
qWarning () << "SIDGUI::sdoVideoError: " << error << m_player -> errorString ();
#ifdef _MSC_VER
// Qt5/Windows doesn't support mp4 by default, so suggest K-Lite codecs
// Qt6 doesn't need these
if ( error == QMediaPlayer :: FormatError ) {
2024-04-05 16:35:43 +01:00
QMessageBox :: warning ( this , "Video Error" , "Unable to play video. Please try installing mp4/h264 codec, such as: <a href='https://www.codecguide.com/download_k-lite_codec_pack_basic.htm'>K-Lite codedcs</a>." );
}
#elif LINUX
if ( error == QMediaPlayer :: FormatError ) {
QMessageBox :: warning ( this , "Video Error" , "Unable to play video. Please try installing mp4/h264 codec, such as gstreamer libav." );
2024-04-02 16:13:01 +01:00
}
#else
if ( error == QMediaPlayer :: FormatError ) {
2024-04-05 16:35:43 +01:00
QMessageBox :: warning ( this , "Video Error" , "Unable to play video. Please try installing an mp4/h264 codec." );
2024-04-02 16:13:01 +01:00
}
#endif
}
void SIDGUI :: sdoVideoStatusChanged ( QMediaPlayer :: MediaStatus status )
{
if ( status == QMediaPlayer :: LoadingMedia )
{
ui -> sdoProgressBar -> setValue ( 0 );
ui -> sdoProgressBar -> setVisible ( true );
}
else if ( status == QMediaPlayer :: BufferedMedia )
{
ui -> sdoProgressBar -> setValue ( 100 );
ui -> sdoProgressBar -> setVisible ( false );
}
else if ( status == QMediaPlayer :: EndOfMedia )
{
m_player -> setPosition ( 0 );
m_player -> play ();
}
}
void SIDGUI :: applyDateTime ()
{
if ( ! m_settings . m_map . isEmpty () && ( m_settings . m_map != "None" ))
{
if ( m_settings . m_sdoNow ) {
FeatureWebAPIUtils :: mapSetDateTime ( QDateTime :: currentDateTime ());
} else {
FeatureWebAPIUtils :: mapSetDateTime ( m_settings . m_sdoDateTime );
}
}
}
void SIDGUI :: on_showSats_clicked ()
{
// Create a Satellite Tracker feature
MainCore * mainCore = MainCore :: instance ();
PluginAPI :: FeatureRegistrations * featureRegistrations = mainCore -> getPluginManager () -> getFeatureRegistrations ();
int nbRegistrations = featureRegistrations -> size ();
int index = 0 ;
for (; index < nbRegistrations ; index ++ )
{
if ( featureRegistrations -> at ( index ). m_featureId == "SatelliteTracker" ) {
break ;
}
}
if ( index < nbRegistrations )
{
connect ( mainCore , & MainCore :: featureAdded , this , & SIDGUI :: onSatTrackerAdded );
MainCore :: MsgAddFeature * msg = MainCore :: MsgAddFeature :: create ( 0 , index );
mainCore -> getMainMessageQueue () -> push ( msg );
}
else
{
QMessageBox :: warning ( this , "Error" , "Satellite Tracker feature not available" );
}
}
void SIDGUI :: onSatTrackerAdded ( int featureSetIndex , Feature * feature )
{
if ( feature -> getURI () == "sdrangel.feature.satellitetracker" )
{
disconnect ( MainCore :: instance (), & MainCore :: featureAdded , this , & SIDGUI :: onSatTrackerAdded );
QJsonArray sats = { "SDO" , "GOES 16" , "GOES-18" };
ChannelWebAPIUtils :: patchFeatureSetting ( featureSetIndex , feature -> getIndexInFeatureSet (), "satellites" , sats );
ChannelWebAPIUtils :: patchFeatureSetting ( featureSetIndex , feature -> getIndexInFeatureSet (), "target" , "SDO" );
ChannelWebAPIUtils :: runFeature ( featureSetIndex , feature -> getIndexInFeatureSet ());
}
}
void SIDGUI :: on_map_currentTextChanged ( const QString & text )
{
m_settings . m_map = text ;
applySetting ( "map" );
applyDateTime ();
}
2024-04-04 21:41:07 +01:00
// Plot paths from transmitters to receivers on map
void SIDGUI :: on_showPaths_clicked ()
{
2024-04-04 21:41:32 +01:00
clearFromMap ();
2024-04-04 21:41:07 +01:00
for ( int i = 0 ; i < m_settings . m_channelSettings . size (); i ++ )
{
unsigned int deviceSetIndex ;
unsigned int channelIndex ;
if ( MainCore :: getDeviceAndChannelIndexFromId ( m_settings . m_channelSettings [ i ]. m_id , deviceSetIndex , channelIndex ))
{
// Get position of device, defaulting to My Position
QGeoCoordinate rxPosition ;
2024-09-17 09:47:45 +01:00
float latitude , longitude , altitude ;
if ( ChannelWebAPIUtils :: getDevicePosition ( deviceSetIndex , latitude , longitude , altitude ))
{
rxPosition . setLatitude ( latitude );
rxPosition . setLongitude ( longitude );
rxPosition . setAltitude ( altitude );
}
else
2024-04-04 21:41:07 +01:00
{
rxPosition . setLatitude ( MainCore :: instance () -> getSettings (). getLatitude ());
rxPosition . setLongitude ( MainCore :: instance () -> getSettings (). getLongitude ());
rxPosition . setAltitude ( MainCore :: instance () -> getSettings (). getAltitude ());
}
// Get position of transmitter
if ( VLFTransmitters :: m_callsignHash . contains ( m_settings . m_channelSettings [ i ]. m_label ))
{
const VLFTransmitters :: Transmitter * transmitter = VLFTransmitters :: m_callsignHash . value ( m_settings . m_channelSettings [ i ]. m_label );
QGeoCoordinate txPosition ;
txPosition . setLatitude ( transmitter -> m_latitude );
txPosition . setLongitude ( transmitter -> m_longitude );
txPosition . setAltitude ( 0 );
// Calculate mid point for position of label
qreal distance = txPosition . distanceTo ( rxPosition );
qreal az = txPosition . azimuthTo ( rxPosition );
QGeoCoordinate midPoint = txPosition . atDistanceAndAzimuth ( distance / 2.0 , az );
// Create a path from transmitter to receiver
QList < ObjectPipe *> mapPipes ;
MainCore :: instance () -> getMessagePipes (). getMessagePipes ( m_sid , "mapitems" , mapPipes );
if ( mapPipes . size () > 0 )
{
for ( const auto & pipe : mapPipes )
{
MessageQueue * messageQueue = qobject_cast < MessageQueue *> ( pipe -> m_element );
SWGSDRangel :: SWGMapItem * swgMapItem = new SWGSDRangel :: SWGMapItem ();
QString deviceId = QString ( "%1%2" ). arg ( m_settings . m_channelSettings [ i ]. m_id [ 0 ]). arg ( deviceSetIndex );
QString name = QString ( "SID %1 to %2" ). arg ( m_settings . m_channelSettings [ i ]. m_label ). arg ( deviceId );
QString details = QString ( "%1<br>Distance: %2 km" ). arg ( name ). arg (( int ) std :: round ( distance / 1000.0 ));
swgMapItem -> setName ( new QString ( name ));
swgMapItem -> setLatitude ( midPoint . latitude ());
swgMapItem -> setLongitude ( midPoint . longitude ());
swgMapItem -> setAltitude ( midPoint . altitude ());
QString image = QString ( "none" );
swgMapItem -> setImage ( new QString ( image ));
swgMapItem -> setImageRotation ( 0 );
swgMapItem -> setText ( new QString ( details )); // Not used - label is used instead for now
swgMapItem -> setFixedPosition ( true );
swgMapItem -> setLabel ( new QString ( details ));
swgMapItem -> setAltitudeReference ( 0 );
QList < SWGSDRangel :: SWGMapCoordinate *> * coords = new QList < SWGSDRangel :: SWGMapCoordinate *> ();
SWGSDRangel :: SWGMapCoordinate * c = new SWGSDRangel :: SWGMapCoordinate ();
c -> setLatitude ( rxPosition . latitude ());
c -> setLongitude ( rxPosition . longitude ());
c -> setAltitude ( rxPosition . altitude ());
coords -> append ( c );
c = new SWGSDRangel :: SWGMapCoordinate ();
c -> setLatitude ( txPosition . latitude ());
c -> setLongitude ( txPosition . longitude ());
c -> setAltitude ( txPosition . altitude ());
coords -> append ( c );
swgMapItem -> setColorValid ( 1 );
swgMapItem -> setColor ( m_settings . m_channelSettings [ i ]. m_color . rgba ());
swgMapItem -> setCoordinates ( coords );
swgMapItem -> setType ( 3 );
MainCore :: MsgMapItem * msg = MainCore :: MsgMapItem :: create ( m_sid , swgMapItem );
messageQueue -> push ( msg );
2024-04-04 21:41:32 +01:00
m_mapItemNames . append ( name );
2024-04-04 21:41:07 +01:00
}
}
}
}
}
2024-04-04 21:41:32 +01:00
}
void SIDGUI :: clearFromMap ()
{
QList < ObjectPipe *> mapPipes ;
MainCore :: instance () -> getMessagePipes (). getMessagePipes ( m_sid , "mapitems" , mapPipes );
2024-04-04 21:41:07 +01:00
2024-04-04 21:41:32 +01:00
for ( const auto & name : m_mapItemNames )
{
for ( const auto & pipe : mapPipes )
{
MessageQueue * messageQueue = qobject_cast < MessageQueue *> ( pipe -> m_element );
SWGSDRangel :: SWGMapItem * swgMapItem = new SWGSDRangel :: SWGMapItem ();
swgMapItem -> setName ( new QString ( name ));
swgMapItem -> setImage ( new QString ( "" ));
swgMapItem -> setType ( 3 );
MainCore :: MsgMapItem * msg = MainCore :: MsgMapItem :: create ( m_sid , swgMapItem );
messageQueue -> push ( msg );
}
}
2024-04-04 21:41:07 +01:00
}
2024-04-02 16:13:01 +01:00
void SIDGUI :: featuresChanged ( const QStringList & renameFrom , const QStringList & renameTo )
{
const AvailableChannelOrFeatureList availableFeatures = m_availableFeatureHandler . getAvailableChannelOrFeatureList ();
if ( renameFrom . contains ( m_settings . m_map ))
{
m_settings . m_map = renameTo [ renameFrom . indexOf ( m_settings . m_map )];
applySetting ( "map" );
}
ui -> map -> blockSignals ( true );
ui -> map -> clear ();
ui -> map -> addItem ( "None" );
for ( const auto & map : availableFeatures ) {
ui -> map -> addItem ( map . getId ());
}
int idx = ui -> map -> findText ( m_settings . m_map );
if ( idx >= 0 ) {
ui -> map -> setCurrentIndex ( idx );
} else {
ui -> map -> setCurrentIndex ( - 1 );
}
ui -> map -> blockSignals ( false );
// If no setting, default to first available map
if ( m_settings . m_map . isEmpty () && ( ui -> map -> count () >= 2 )) {
ui -> map -> setCurrentIndex ( 1 );
}
}
2024-04-04 15:19:35 +01:00
void SIDGUI :: channelsChanged ( const QStringList & renameFrom , const QStringList & renameTo , const QStringList & removed , const QStringList & added )
{
removeChannels ( removed );
// Rename measurements and settings that have had their id changed
for ( int i = 0 ; i < renameFrom . size (); i ++ )
{
for ( int j = 0 ; j < m_channelMeasurements . size (); j ++ )
{
if ( m_channelMeasurements [ j ]. m_id == renameFrom [ i ]) {
m_channelMeasurements [ j ]. m_id = renameTo [ i ];
}
}
for ( int j = 0 ; j < m_settings . m_channelSettings . size (); j ++ )
{
if ( m_settings . m_channelSettings [ j ]. m_id == renameFrom [ i ]) {
m_settings . m_channelSettings [ j ]. m_id = renameTo [ i ];
}
}
}
// Create settings for any new channels
// Don't call createChannelSettings when channels are removed, as ids might not have been updated yet
if ( added . size () > 0 )
{
if ( m_settings . createChannelSettings ()) {
applySetting ( "channelSettings" );
}
}
}
void SIDGUI :: removeChannels ( const QStringList & ids )
{
for ( int i = 0 ; i < ids . size (); i ++ )
{
for ( int j = 0 ; j < m_channelMeasurements . size (); j ++ )
{
if ( ids [ i ] == m_channelMeasurements [ j ]. m_id )
{
m_channelMeasurements . removeAt ( j );
break ;
}
}
for ( int j = 0 ; j < m_settings . m_channelSettings . size (); j ++ )
{
if ( ids [ i ] == m_settings . m_channelSettings [ j ]. m_id )
{
m_settings . m_channelSettings . removeAt ( j );
break ;
}
}
}
}
2024-04-02 16:13:01 +01:00
void SIDGUI :: autosave ()
{
qDebug () << "SIDGUI::autosave start" ;
writeCSV ( m_settings . m_filename );
qDebug () << "SIDGUI::autosave done" ;
}
void SIDGUI :: on_saveData_clicked ()
{
2025-09-07 10:57:29 +01:00
m_fileDialog . setDefaultSuffix ( "csv" );
2024-04-02 16:13:01 +01:00
m_fileDialog . setAcceptMode ( QFileDialog :: AcceptSave );
if ( m_fileDialog . exec ())
{
QStringList fileNames = m_fileDialog . selectedFiles ();
if ( fileNames . size () > 0 ) {
writeCSV ( fileNames [ 0 ]);
}
}
}
void SIDGUI :: on_loadData_clicked ()
{
m_fileDialog . setAcceptMode ( QFileDialog :: AcceptOpen );
if ( m_fileDialog . exec ())
{
QStringList fileNames = m_fileDialog . selectedFiles ();
if ( fileNames . size () > 0 ) {
readCSV ( fileNames [ 0 ], false );
}
}
}
void SIDGUI :: writeCSV ( const QString & filename )
{
if ( m_channelMeasurements . size () < 1 ) {
return ;
}
QFile file ( filename );
if ( ! file . open ( QIODevice :: WriteOnly | QIODevice :: Text ))
{
QMessageBox :: critical ( this , "SID" , QString ( "Failed to open file %1" ). arg ( filename ));
return ;
}
QTextStream out ( & file );
// Create a CSV file from the values in the table
QList < int > idx ;
QList < ChannelMeasurement *> measurements ;
out << "Date and Time," ;
for ( int i = 0 ; i < m_channelMeasurements . size (); i ++ )
{
SIDSettings :: ChannelSettings * channelSettings = m_settings . getChannelSettings ( m_channelMeasurements [ i ]. m_id );
QString name = m_channelMeasurements [ i ]. m_id ;
if ( channelSettings )
{
name . append ( "-" );
name . append ( channelSettings -> m_label );
}
out << name << "," ;
measurements . append ( & m_channelMeasurements [ i ]);
idx . append ( 0 );
}
out << "X-Ray Primary Short," ;
measurements . append ( & m_xrayShortMeasurements [ 0 ]);
idx . append ( 0 );
out << "X-Ray Primary Long," ;
measurements . append ( & m_xrayLongMeasurements [ 0 ]);
idx . append ( 0 );
out << "X-Ray Secondary Short," ;
measurements . append ( & m_xrayShortMeasurements [ 1 ]);
idx . append ( 0 );
out << "X-Ray Secondary Long," ;
measurements . append ( & m_xrayLongMeasurements [ 1 ]);
idx . append ( 0 );
for ( int i = 0 ; i < 4 ; i ++ )
{
out << QString ( "%1 Proton," ). arg ( SIDGUI :: m_protonEnergies [ i ]);
measurements . append ( & m_protonMeasurements [ i ]);
idx . append ( 0 );
}
out << " \n " ;
// Find earliest time
QDateTime t ;
for ( int i = 0 ; i < measurements . size (); i ++ )
{
ChannelMeasurement * cm = measurements [ i ];
Measurement * m = & cm -> m_measurements [ idx [ i ]];
if ( ! t . isValid () || ( m -> m_dateTime < t )) {
t = m -> m_dateTime ;
}
}
bool done = false ;
while ( ! done )
{
out << t . toUTC (). toString ( Qt :: ISODateWithMs );
out << "," ;
// Output data at this time
for ( int i = 0 ; i < measurements . size (); i ++ )
{
ChannelMeasurement * cm = measurements [ i ];
if ( cm -> m_measurements . size () > idx [ i ])
{
Measurement * m = & cm -> m_measurements [ idx [ i ]];
if ( m -> m_dateTime == t )
{
out << m -> m_measurement ;
idx [ i ] ++ ;
}
}
out << "," ;
}
out << " \n " ;
// Find next time
t = QDateTime ();
for ( int i = 0 ; i < measurements . size (); i ++ )
{
ChannelMeasurement * cm = measurements [ i ];
if ( cm -> m_measurements . size () > idx [ i ])
{
Measurement * m = & cm -> m_measurements [ idx [ i ]];
if ( ! t . isValid () || ( m -> m_dateTime < t )) {
t = m -> m_dateTime ;
}
}
}
if ( ! t . isValid ()) {
done = true ;
}
}
}
void SIDGUI :: readCSV ( const QString & filename , bool autoload )
{
QFile file ( filename );
if ( ! file . open ( QIODevice :: ReadOnly | QIODevice :: Text ))
{
if ( ! autoload ) {
QMessageBox :: critical ( this , "SID" , QString ( "Failed to open file %1" ). arg ( filename ));
}
return ;
}
QTextStream in ( & file );
// Prevent data updates while reading CSV
disconnectDataUpdates ();
// Delete existing data
clearAllData ();
// Get list of colors to use
QList < QRgb > colors = SIDSettings :: m_defaultColors ;
for ( const auto & channelSettings : m_settings . m_channelSettings ) {
colors . removeAll ( channelSettings . m_color . rgb ());
}
2024-04-04 15:19:35 +01:00
bool channelSettingsChanged = false ;
2024-04-02 16:13:01 +01:00
QStringList colNames ;
if ( CSV :: readRow ( in , & colNames ))
{
QList < ChannelMeasurement *> measurements ;
2025-01-17 13:31:19 +00:00
QList < int > measurementIdx ;
for ( int i = 0 ; i < colNames . size () - 1 ; i ++ )
{
2024-04-02 16:13:01 +01:00
measurements . append ( nullptr );
2025-01-17 13:31:19 +00:00
measurementIdx . append ( - 1 );
2024-04-02 16:13:01 +01:00
}
for ( int i = 1 ; i < colNames . size (); i ++ )
{
QString name = colNames [ i ];
if ( name == "X-Ray Primary Short" )
{
measurements [ i - 1 ] = & m_xrayShortMeasurements [ 0 ];
}
else if ( name == "X-Ray Primary Long" )
{
measurements [ i - 1 ] = & m_xrayLongMeasurements [ 0 ];
}
else if ( name == "X-Ray Secondary Short" )
{
measurements [ i - 1 ] = & m_xrayShortMeasurements [ 1 ];
}
else if ( name == "X-Ray Secondary Long" )
{
measurements [ i - 1 ] = & m_xrayLongMeasurements [ 1 ];
}
else if ( name . endsWith ( "Proton" ))
{
for ( int j = 0 ; j < m_protonEnergies . size (); j ++ )
{
if ( name . startsWith ( m_protonEnergies [ j ]))
{
measurements [ i - 1 ] = & m_protonMeasurements [ j ];
break ;
}
}
}
else if ( name . contains ( ":" ))
{
QString id ;
int idx = name . indexOf ( '-' );
if ( idx >= 0 ) {
id = name . left ( idx );
} else {
id = name ;
}
2025-01-17 13:31:19 +00:00
addMeasurements ( id );
measurementIdx [ i - 1 ] = m_channelMeasurements . size () - 1 ;
2024-04-02 16:13:01 +01:00
// Create settings, if we don't have them
SIDSettings :: ChannelSettings * channelSettings = m_settings . getChannelSettings ( id );
if ( ! channelSettings )
{
if ( colors . size () == 0 ) {
colors = SIDSettings :: m_defaultColors ;
}
SIDSettings :: ChannelSettings newSettings ;
newSettings . m_id = id ;
newSettings . m_enabled = true ;
newSettings . m_label = name . mid ( idx + 1 );
newSettings . m_color = colors . takeFirst ();
m_settings . m_channelSettings . append ( newSettings );
2024-04-04 15:19:35 +01:00
channelSettingsChanged = true ;
2024-04-02 16:13:01 +01:00
}
}
}
QMessageBox dialog ( this );
dialog . setText ( "Reading data" );
dialog . addButton ( QMessageBox :: Cancel );
dialog . show ();
QApplication :: processEvents ();
bool cancelled = false ;
QStringList cols ;
int row = 1 ;
while ( ! cancelled && CSV :: readRow ( in , & cols ))
{
if ( cols . size () == measurements . size () + 1 )
{
QDateTime dateTime = QDateTime :: fromString ( cols [ 0 ], Qt :: ISODateWithMs );
for ( int i = 0 ; i < measurements . size (); i ++ )
{
QString valueStr = cols [ i + 1 ];
if ( ! valueStr . isEmpty ())
{
double value = valueStr . toDouble ();
2025-01-17 13:31:19 +00:00
if ( measurements [ i ]) {
measurements [ i ] -> append ( dateTime , value , false );
} else {
m_channelMeasurements [ measurementIdx [ i ]]. append ( dateTime , value , false );
}
2024-04-02 16:13:01 +01:00
}
}
}
else
{
qDebug () << "SIDGUI::readCSV: Not enough data on row " << row ;
}
if ( row % 10000 == 0 )
{
QApplication :: processEvents ();
if ( dialog . clickedButton ()) {
cancelled = true ;
}
}
row ++ ;
}
dialog . close ();
autoscaleX ();
autoscaleY ();
plotChart ();
connectDataUpdates ();
getData ();
2024-04-04 15:19:35 +01:00
if ( channelSettingsChanged ) {
applySetting ( "channelSettings" );
}
2024-04-02 16:13:01 +01:00
}
}
void SIDGUI :: on_saveChartImage_clicked ()
{
QFileDialog fileDialog ( nullptr , "Select file to save image to" , "" , "*.png *.jpg *.jpeg *.bmp *.ppm *.xbm *.xpm" );
fileDialog . setAcceptMode ( QFileDialog :: AcceptSave );
if ( fileDialog . exec ())
{
QStringList fileNames = fileDialog . selectedFiles ();
if ( fileNames . size () > 0 )
{
QImage image ( ui -> chart -> size (), QImage :: Format_ARGB32 );
image . fill ( Qt :: transparent );
QPainter painter ( & image );
ui -> chart -> render ( & painter );
if ( ! image . save ( fileNames [ 0 ])) {
QMessageBox :: critical ( this , "SID" , QString ( "Failed to save image to %1" ). arg ( fileNames [ 0 ]));
}
}
}
}