From 0ca0eb8f733d9cc739621c4b5612f253bb3a0155 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 9 Apr 2022 13:38:22 +0200 Subject: [PATCH] Massive UI revamping (v7): devices intermediate --- doc/img/channel.xcf | Bin 0 -> 36585 bytes doc/img/tool.xcf | Bin 14271 -> 27899 bytes .../bladerf2mimo/bladerf2mimogui.ui | 2 +- .../samplemimo/limesdrmimo/limesdrmimogui.ui | 2 +- plugins/samplemimo/metismiso/metismisogui.ui | 2 +- .../plutosdrmimo/plutosdrmimogui.ui | 2 +- plugins/samplemimo/testmi/testmigui.ui | 2 +- .../samplemimo/testmosync/testmosyncgui.ui | 2 +- plugins/samplemimo/xtrxmimo/xtrxmimogui.ui | 2 +- .../bladerf1output/bladerf1outputgui.ui | 2 +- .../bladerf2output/bladerf2outputgui.ui | 2 +- .../samplesink/fileoutput/fileoutputgui.ui | 2 +- .../hackrfoutput/hackrfoutputgui.ui | 2 +- .../limesdroutput/limesdroutputgui.ui | 2 +- .../plutosdroutput/plutosdroutputgui.ui | 2 +- .../soapysdroutput/soapysdroutputgui.ui | 2 +- plugins/samplesink/testsink/testsinkgui.ui | 2 +- .../samplesink/usrpoutput/usrpoutputgui.ui | 2 +- .../samplesink/xtrxoutput/xtrxoutputgui.ui | 2 +- plugins/samplesource/airspy/airspygui.ui | 2 +- plugins/samplesource/airspyhf/airspyhfgui.ui | 2 +- .../bladerf1input/bladerf1inputgui.ui | 2 +- .../bladerf2input/bladerf2inputgui.ui | 2 +- plugins/samplesource/fcdpro/fcdprogui.ui | 2 +- .../samplesource/fcdproplus/fcdproplusgui.ui | 2 +- .../hackrfinput/hackrfinputgui.ui | 29 +- plugins/samplesource/kiwisdr/kiwisdrgui.ui | 2 +- .../limesdrinput/limesdrinputgui.ui | 2 +- plugins/samplesource/perseus/perseusgui.ui | 2 +- .../plutosdrinput/plutosdrinputgui.ui | 2 +- plugins/samplesource/rtlsdr/rtlsdrgui.ui | 2 +- plugins/samplesource/sdrplay/sdrplaygui.ui | 2 +- .../samplesource/sdrplayv3/sdrplayv3gui.ui | 2 +- .../soapysdrinput/soapysdrinputgui.ui | 2 +- .../samplesource/testsource/testsourcegui.ui | 2 +- .../samplesource/usrpinput/usrpinputgui.ui | 2 +- .../samplesource/xtrxinput/xtrxinputgui.ui | 2 +- sdrbase/device/deviceenumerator.cpp | 89 +++++ sdrbase/device/deviceenumerator.h | 10 + sdrbase/settings/configuration.cpp | 28 +- sdrbase/settings/configuration.h | 8 +- sdrbase/settings/preset.cpp | 63 ++-- sdrbase/settings/preset.h | 33 +- sdrgui/CMakeLists.txt | 4 + sdrgui/device/devicegui.cpp | 36 +- sdrgui/device/devicegui.h | 3 + sdrgui/device/deviceuiset.cpp | 73 +++- sdrgui/device/deviceuiset.h | 33 +- sdrgui/feature/featuregui.cpp | 10 +- sdrgui/feature/featureuiset.cpp | 70 +--- sdrgui/feature/featureuiset.h | 9 +- sdrgui/gui/featurepresetsdialog.cpp | 4 +- sdrgui/gui/featurepresetsdialog.h | 6 +- sdrgui/gui/glspectrum.cpp | 7 +- sdrgui/gui/workspace.cpp | 4 +- sdrgui/mainspectrum/mainspectrumgui.cpp | 290 ++++++++++++++++ sdrgui/mainspectrum/mainspectrumgui.h | 110 ++++++ sdrgui/mainwindow.cpp | 317 ++++++++++++------ sdrgui/mainwindow.h | 21 +- sdrgui/resources/channels.png | Bin 0 -> 4926 bytes sdrgui/resources/channels_add.png | Bin 0 -> 5398 bytes sdrgui/resources/res.qrc | 4 + sdrgui/resources/tool_add.png | Bin 0 -> 5953 bytes sdrgui/resources/tool_star.png | Bin 0 -> 5865 bytes 64 files changed, 1039 insertions(+), 290 deletions(-) create mode 100644 doc/img/channel.xcf create mode 100644 sdrgui/mainspectrum/mainspectrumgui.cpp create mode 100644 sdrgui/mainspectrum/mainspectrumgui.h create mode 100644 sdrgui/resources/channels.png create mode 100644 sdrgui/resources/channels_add.png create mode 100644 sdrgui/resources/tool_add.png create mode 100644 sdrgui/resources/tool_star.png diff --git a/doc/img/channel.xcf b/doc/img/channel.xcf new file mode 100644 index 0000000000000000000000000000000000000000..61cbff5430bf3e8105778263b575fdae6730e9e6 GIT binary patch literal 36585 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 literal 0 HcmV?d00001 diff --git a/doc/img/tool.xcf b/doc/img/tool.xcf index 94adf6f710ea71d6fc15270333fc4d47a71e016a..5d181b4c1c47ab65ae6c29fb9171a0f48fac5cbe 100644 GIT binary patch delta 14000 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 delta 653 zcmXxgODIH97zglkZu2mCn`SZ?#vtR>y~8|AifA_Mu$XRa5Ji(IuLbEwSxM+FEHrrx zvA-*&Bx|LtX2ZrbYhfWN=g$B0)z|O-zjI&b{ABrE>R`wvF~;1YEuy(-7h}@nVIt3S z#sH6VYmcJHK|ReIs?@RBCHV^NTd>GOE|5#)I=M~WLPri3hhWJyEZZR8$?t+1D>t_- zszQgAAJBOQT{3h(!0K^wf}DY#Pgwf~>qek=4%XAUZy(urWU4XdJHen)oV{88JZ!pv z&9sx2EOP5P3~Z7+Bpr0%1-Wevws*pgFW8lVa#AS1&y*Mj${;yRj*==2(vNiiB8R47 dm|h&FdBlo&q?DwMMZA=6V04l( Liberation Mono - 20 + 16 diff --git a/plugins/samplemimo/limesdrmimo/limesdrmimogui.ui b/plugins/samplemimo/limesdrmimo/limesdrmimogui.ui index 818a5d5cd..bdc8329e5 100644 --- a/plugins/samplemimo/limesdrmimo/limesdrmimogui.ui +++ b/plugins/samplemimo/limesdrmimo/limesdrmimogui.ui @@ -336,7 +336,7 @@ Liberation Mono - 20 + 16 50 false diff --git a/plugins/samplemimo/metismiso/metismisogui.ui b/plugins/samplemimo/metismiso/metismisogui.ui index df0c87514..bf3a59e48 100644 --- a/plugins/samplemimo/metismiso/metismisogui.ui +++ b/plugins/samplemimo/metismiso/metismisogui.ui @@ -306,7 +306,7 @@ Liberation Mono - 20 + 16 50 false false diff --git a/plugins/samplemimo/plutosdrmimo/plutosdrmimogui.ui b/plugins/samplemimo/plutosdrmimo/plutosdrmimogui.ui index 5434ff697..2f0ef9104 100644 --- a/plugins/samplemimo/plutosdrmimo/plutosdrmimogui.ui +++ b/plugins/samplemimo/plutosdrmimo/plutosdrmimogui.ui @@ -313,7 +313,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplemimo/testmi/testmigui.ui b/plugins/samplemimo/testmi/testmigui.ui index 078537460..0baf16c66 100644 --- a/plugins/samplemimo/testmi/testmigui.ui +++ b/plugins/samplemimo/testmi/testmigui.ui @@ -223,7 +223,7 @@ Liberation Mono - 20 + 16 50 false false diff --git a/plugins/samplemimo/testmosync/testmosyncgui.ui b/plugins/samplemimo/testmosync/testmosyncgui.ui index 104df588a..e80a8714b 100644 --- a/plugins/samplemimo/testmosync/testmosyncgui.ui +++ b/plugins/samplemimo/testmosync/testmosyncgui.ui @@ -131,7 +131,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplemimo/xtrxmimo/xtrxmimogui.ui b/plugins/samplemimo/xtrxmimo/xtrxmimogui.ui index c59b43367..1cf55b59d 100644 --- a/plugins/samplemimo/xtrxmimo/xtrxmimogui.ui +++ b/plugins/samplemimo/xtrxmimo/xtrxmimogui.ui @@ -336,7 +336,7 @@ Liberation Mono - 20 + 16 50 false diff --git a/plugins/samplesink/bladerf1output/bladerf1outputgui.ui b/plugins/samplesink/bladerf1output/bladerf1outputgui.ui index d8260c1d4..2c2a63bc9 100644 --- a/plugins/samplesink/bladerf1output/bladerf1outputgui.ui +++ b/plugins/samplesink/bladerf1output/bladerf1outputgui.ui @@ -128,7 +128,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui index a589a1d9c..fe54b2964 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui @@ -128,7 +128,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesink/fileoutput/fileoutputgui.ui b/plugins/samplesink/fileoutput/fileoutputgui.ui index 82c4c8094..a6c19111a 100644 --- a/plugins/samplesink/fileoutput/fileoutputgui.ui +++ b/plugins/samplesink/fileoutput/fileoutputgui.ui @@ -131,7 +131,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui index 8b9e1e455..b27ca6e16 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui +++ b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui @@ -131,7 +131,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.ui b/plugins/samplesink/limesdroutput/limesdroutputgui.ui index 7fe2c2758..32b2efb93 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.ui +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.ui @@ -128,7 +128,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui b/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui index 8dc5b657d..2220656be 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui +++ b/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui @@ -128,7 +128,7 @@ Liberation Mono - 20 + 16 50 false diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui b/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui index 985bb5c90..4ae211477 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui @@ -116,7 +116,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesink/testsink/testsinkgui.ui b/plugins/samplesink/testsink/testsinkgui.ui index 1d491e993..65c8354e8 100644 --- a/plugins/samplesink/testsink/testsinkgui.ui +++ b/plugins/samplesink/testsink/testsinkgui.ui @@ -131,7 +131,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesink/usrpoutput/usrpoutputgui.ui b/plugins/samplesink/usrpoutput/usrpoutputgui.ui index 3e0d3ba25..fb66c98d9 100644 --- a/plugins/samplesink/usrpoutput/usrpoutputgui.ui +++ b/plugins/samplesink/usrpoutput/usrpoutputgui.ui @@ -138,7 +138,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesink/xtrxoutput/xtrxoutputgui.ui b/plugins/samplesink/xtrxoutput/xtrxoutputgui.ui index c04dd6267..349c33dda 100644 --- a/plugins/samplesink/xtrxoutput/xtrxoutputgui.ui +++ b/plugins/samplesink/xtrxoutput/xtrxoutputgui.ui @@ -128,7 +128,7 @@ Liberation Mono - 20 + 16 50 false diff --git a/plugins/samplesource/airspy/airspygui.ui b/plugins/samplesource/airspy/airspygui.ui index 1cf50415d..cd4ea6f06 100644 --- a/plugins/samplesource/airspy/airspygui.ui +++ b/plugins/samplesource/airspy/airspygui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/airspyhf/airspyhfgui.ui b/plugins/samplesource/airspyhf/airspyhfgui.ui index 40463d73b..c19aae71c 100644 --- a/plugins/samplesource/airspyhf/airspyhfgui.ui +++ b/plugins/samplesource/airspyhf/airspyhfgui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/bladerf1input/bladerf1inputgui.ui b/plugins/samplesource/bladerf1input/bladerf1inputgui.ui index c0321330a..531eb3311 100644 --- a/plugins/samplesource/bladerf1input/bladerf1inputgui.ui +++ b/plugins/samplesource/bladerf1input/bladerf1inputgui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui index 7ec663a32..03fd1f2e3 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/fcdpro/fcdprogui.ui b/plugins/samplesource/fcdpro/fcdprogui.ui index feae1eeb4..7af12e90b 100644 --- a/plugins/samplesource/fcdpro/fcdprogui.ui +++ b/plugins/samplesource/fcdpro/fcdprogui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/fcdproplus/fcdproplusgui.ui b/plugins/samplesource/fcdproplus/fcdproplusgui.ui index 8c414c5ed..319ff8471 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusgui.ui +++ b/plugins/samplesource/fcdproplus/fcdproplusgui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/hackrfinput/hackrfinputgui.ui b/plugins/samplesource/hackrfinput/hackrfinputgui.ui index 65d063e37..d28afc6ba 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputgui.ui +++ b/plugins/samplesource/hackrfinput/hackrfinputgui.ui @@ -6,8 +6,8 @@ 0 0 - 310 - 300 + 360 + 272 @@ -18,8 +18,8 @@ - 310 - 300 + 360 + 0 @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 @@ -243,7 +243,7 @@ - + Bandpass Filter auto select @@ -658,23 +658,6 @@ - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - diff --git a/plugins/samplesource/kiwisdr/kiwisdrgui.ui b/plugins/samplesource/kiwisdr/kiwisdrgui.ui index bbf6bec05..16a4c787d 100644 --- a/plugins/samplesource/kiwisdr/kiwisdrgui.ui +++ b/plugins/samplesource/kiwisdr/kiwisdrgui.ui @@ -131,7 +131,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.ui b/plugins/samplesource/limesdrinput/limesdrinputgui.ui index 2d9d98b37..8bb19d189 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.ui +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.ui @@ -128,7 +128,7 @@ Liberation Mono - 20 + 16 50 false diff --git a/plugins/samplesource/perseus/perseusgui.ui b/plugins/samplesource/perseus/perseusgui.ui index 1d457ba29..c06331a1d 100644 --- a/plugins/samplesource/perseus/perseusgui.ui +++ b/plugins/samplesource/perseus/perseusgui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui b/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui index 336794851..709d10937 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui @@ -128,7 +128,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.ui b/plugins/samplesource/rtlsdr/rtlsdrgui.ui index d4a6d75af..df02e0cd5 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrgui.ui +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/sdrplay/sdrplaygui.ui b/plugins/samplesource/sdrplay/sdrplaygui.ui index e9d07e273..f22caa9a8 100644 --- a/plugins/samplesource/sdrplay/sdrplaygui.ui +++ b/plugins/samplesource/sdrplay/sdrplaygui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/sdrplayv3/sdrplayv3gui.ui b/plugins/samplesource/sdrplayv3/sdrplayv3gui.ui index f4665f4f5..bbf4c43c0 100644 --- a/plugins/samplesource/sdrplayv3/sdrplayv3gui.ui +++ b/plugins/samplesource/sdrplayv3/sdrplayv3gui.ui @@ -122,7 +122,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index ee59b8d69..f0781845a 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -116,7 +116,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui index d5768cdd8..ce3915af9 100644 --- a/plugins/samplesource/testsource/testsourcegui.ui +++ b/plugins/samplesource/testsource/testsourcegui.ui @@ -131,7 +131,7 @@ Liberation Mono - 20 + 16 diff --git a/plugins/samplesource/usrpinput/usrpinputgui.ui b/plugins/samplesource/usrpinput/usrpinputgui.ui index 80be6c42a..bbdafa362 100644 --- a/plugins/samplesource/usrpinput/usrpinputgui.ui +++ b/plugins/samplesource/usrpinput/usrpinputgui.ui @@ -138,7 +138,7 @@ Liberation Mono - 20 + 16 50 false diff --git a/plugins/samplesource/xtrxinput/xtrxinputgui.ui b/plugins/samplesource/xtrxinput/xtrxinputgui.ui index de4b9edce..8078a54e3 100644 --- a/plugins/samplesource/xtrxinput/xtrxinputgui.ui +++ b/plugins/samplesource/xtrxinput/xtrxinputgui.ui @@ -128,7 +128,7 @@ Liberation Mono - 20 + 16 50 false diff --git a/sdrbase/device/deviceenumerator.cpp b/sdrbase/device/deviceenumerator.cpp index c0ddec020..f4ecd56c9 100644 --- a/sdrbase/device/deviceenumerator.cpp +++ b/sdrbase/device/deviceenumerator.cpp @@ -506,3 +506,92 @@ int DeviceEnumerator::getMIMOSamplingDeviceIndex(const QString& deviceId, int se return -1; } + +int DeviceEnumerator::getBestRxSamplingDeviceIndex(const QString& deviceId, const QString& deviceSerial, int deviceSequence, int deviceItemIndex) +{ + return getBestSamplingDeviceIndex(m_rxEnumeration, deviceId, deviceSerial, deviceSequence, deviceItemIndex); +} + +int DeviceEnumerator::getBestTxSamplingDeviceIndex(const QString& deviceId, const QString& deviceSerial, int deviceSequence, int deviceItemIndex) +{ + return getBestSamplingDeviceIndex(m_txEnumeration, deviceId, deviceSerial, deviceSequence, deviceItemIndex); +} + +int DeviceEnumerator::getBestMIMOSamplingDeviceIndex(const QString& deviceId, const QString& deviceSerial, int deviceSequence) +{ + return getBestSamplingDeviceIndex(m_mimoEnumeration, deviceId, deviceSerial, deviceSequence, -1); +} + +int DeviceEnumerator::getBestSamplingDeviceIndex( + const DevicesEnumeration& devicesEnumeration, + const QString& deviceId, + const QString& deviceSerial, + int deviceSequence, + int deviceItemIndex +) +{ + DevicesEnumeration::const_iterator it = devicesEnumeration.begin(); + DevicesEnumeration::const_iterator itFirstOfKind = devicesEnumeration.end(); + DevicesEnumeration::const_iterator itMatchSequence = devicesEnumeration.end(); + + for (; it != devicesEnumeration.end(); ++it) + { + if ((it->m_samplingDevice.id == deviceId) && + ( + ((deviceItemIndex < 0) || (deviceItemIndex > it->m_samplingDevice.deviceNbItems)) || // take first if item index is negative or out of range + ((deviceItemIndex <= it->m_samplingDevice.deviceNbItems) && (deviceItemIndex == it->m_samplingDevice.deviceItemIndex)) // take exact item index if in range + ) + ) + { + if (itFirstOfKind == devicesEnumeration.end()) { + itFirstOfKind = it; + } + + if (deviceSerial.isNull() || deviceSerial.isEmpty()) + { + if (it->m_samplingDevice.sequence == deviceSequence) { + break; + } + } + else + { + if (it->m_samplingDevice.serial == deviceSerial) { + break; + } else if(it->m_samplingDevice.sequence == deviceSequence) { + itMatchSequence = it; + } + } + } + } + + if (it == devicesEnumeration.end()) // no exact match + { + if (itMatchSequence != devicesEnumeration.end()) // match sequence and device type ? + { + qDebug("DeviceEnumerator::getBestSamplingDeviceIndex: sequence matched: id: %s ser: %s seq: %d", + qPrintable(itMatchSequence->m_samplingDevice.id), + qPrintable(itMatchSequence->m_samplingDevice.serial), + itMatchSequence->m_samplingDevice.sequence); + return itMatchSequence - devicesEnumeration.begin(); + } + else if (itFirstOfKind != devicesEnumeration.end()) // match just device type ? + { + qDebug("DeviceEnumerator::getBestSamplingDeviceIndex: first of kind matched: id: %s ser: %s seq: %d", + qPrintable(itFirstOfKind->m_samplingDevice.id), + qPrintable(itFirstOfKind->m_samplingDevice.serial), + itFirstOfKind->m_samplingDevice.sequence); + return itFirstOfKind - devicesEnumeration.begin(); + } + else // definitely not found ! + { + qDebug("DeviceEnumerator::getBestSamplingDeviceIndex: no match"); + return -1; + } + } + else // exact match + { + qDebug("DeviceEnumerator::getBestSamplingDeviceIndex: serial matched (exact): id: %s ser: %s", + qPrintable(it->m_samplingDevice.id), qPrintable(it->m_samplingDevice.serial)); + return it - devicesEnumeration.begin(); + } +} diff --git a/sdrbase/device/deviceenumerator.h b/sdrbase/device/deviceenumerator.h index 3ef4f0489..3cd7e68d4 100644 --- a/sdrbase/device/deviceenumerator.h +++ b/sdrbase/device/deviceenumerator.h @@ -62,6 +62,9 @@ public: int getRxSamplingDeviceIndex(const QString& deviceId, int sequence, int deviceItemIndex); int getTxSamplingDeviceIndex(const QString& deviceId, int sequence, int deviceItemIndex); int getMIMOSamplingDeviceIndex(const QString& deviceId, int sequence); + int getBestRxSamplingDeviceIndex(const QString& deviceId, const QString& serial, int sequence, int deviceItemIndex); + int getBestTxSamplingDeviceIndex(const QString& deviceId, const QString& serial, int sequence, int deviceItemIndex); + int getBestMIMOSamplingDeviceIndex(const QString& deviceId, const QString& serial, int sequence); private: struct DeviceEnumeration @@ -91,6 +94,13 @@ private: bool isRxEnumerated(const QString& deviceHwId, int deviceSequence); bool isTxEnumerated(const QString& deviceHwId, int deviceSequence); bool isMIMOEnumerated(const QString& deviceHwId, int deviceSequence); + int getBestSamplingDeviceIndex( + const DevicesEnumeration& devicesEnumeration, + const QString& deviceId, + const QString& serial, + int sequence, + int deviceItemIndex + ); }; #endif /* SDRBASE_DEVICE_DEVICEENUMERATOR_H_ */ diff --git a/sdrbase/settings/configuration.cpp b/sdrbase/settings/configuration.cpp index 91210fa91..d2ef6ebcd 100644 --- a/sdrbase/settings/configuration.cpp +++ b/sdrbase/settings/configuration.cpp @@ -44,12 +44,20 @@ QByteArray Configuration::serialize() const QByteArray b = m_featureSetPreset.serialize(); s.writeBlob(3, b); - s.writeS32(100, m_workspaceGeometries.size()); + int nitems = m_workspaceGeometries.size() < 99 ? m_workspaceGeometries.size() : 99; + s.writeS32(100, nitems); - for(int i = 0; i < m_workspaceGeometries.size(); i++) { + for (int i = 0; i < nitems; i++) { s.writeBlob(101 + i, m_workspaceGeometries[i]); } + nitems = m_deviceSetPresets.size() < 99 ? m_deviceSetPresets.size() : 99; + s.writeS32(200, nitems); + + for (int i = 0; i < nitems; i++) { + s.writeBlob(201 + i, m_deviceSetPresets[i].serialize()); + } + return s.final(); } @@ -72,15 +80,24 @@ bool Configuration::deserialize(const QByteArray& data) d.readBlob(3, &b); m_featureSetPreset.deserialize(b); - int nbWorkspaces; - d.readS32(100, &nbWorkspaces, 0); + int nitems; + d.readS32(100, &nitems, 0); - for(int i = 0; i < nbWorkspaces; i++) + for(int i = 0; i < nitems; i++) { m_workspaceGeometries.push_back(QByteArray()); d.readBlob(101 + i, &m_workspaceGeometries.back()); } + d.readS32(200, &nitems, 0); + + for (int i = 0; i < nitems; i++) + { + d.readBlob(201 + i, &b); + m_deviceSetPresets.push_back(Preset()); + m_deviceSetPresets.back().deserialize(b); + } + return true; } else @@ -97,6 +114,7 @@ int Configuration::getNumberOfWorkspaces() const void Configuration::clearData() { + m_deviceSetPresets.clear(); m_featureSetPreset.clearFeatures(); m_workspaceGeometries.clear(); } diff --git a/sdrbase/settings/configuration.h b/sdrbase/settings/configuration.h index cbdbc37ed..d08d51371 100644 --- a/sdrbase/settings/configuration.h +++ b/sdrbase/settings/configuration.h @@ -26,6 +26,7 @@ #include #include "featuresetpreset.h" +#include "preset.h" #include "export.h" class SDRBASE_API WorkspaceConfiguration { @@ -47,10 +48,12 @@ public: const QString& getDescription() const { return m_description; } int getNumberOfWorkspaces() const; - FeatureSetPreset& getFeatureSetPreset() { return m_featureSetPreset; } - const FeatureSetPreset& getFeatureSetPreset() const { return m_featureSetPreset; } QList& getWorkspaceGeometries() { return m_workspaceGeometries; } const QList& getWorkspaceGeometries() const { return m_workspaceGeometries; } + FeatureSetPreset& getFeatureSetPreset() { return m_featureSetPreset; } + const FeatureSetPreset& getFeatureSetPreset() const { return m_featureSetPreset; } + QList& getDeviceSetPresets() { return m_deviceSetPresets; } + const QList& getDeviceSetPresets() const { return m_deviceSetPresets; } void clearData(); static bool configCompare(const Configuration *p1, Configuration *p2) @@ -70,6 +73,7 @@ private: QString m_description; QList m_workspaceGeometries; FeatureSetPreset m_featureSetPreset; + QList m_deviceSetPresets; }; Q_DECLARE_METATYPE(const Configuration*) diff --git a/sdrbase/settings/preset.cpp b/sdrbase/settings/preset.cpp index cc4e00f7c..4cb5b598a 100644 --- a/sdrbase/settings/preset.cpp +++ b/sdrbase/settings/preset.cpp @@ -72,6 +72,14 @@ QByteArray Preset::serialize() const s.writeBool(6, m_presetType == PresetSource); s.writeS32(7, (int) m_presetType); s.writeBool(8, m_showSpectrum); + s.writeBlob(9, m_spectrumGeometry); + s.writeS32(10, m_spectrumWorkspaceIndex); + s.writeBlob(11, m_deviceGeometry); + s.writeS32(12, m_deviceWorkspaceIndex); + s.writeString(13, m_selectedDevice.m_deviceId); + s.writeString(14, m_selectedDevice.m_deviceSerial); + s.writeS32(15, m_selectedDevice.m_deviceSequence); + s.writeS32(16, m_selectedDevice.m_deviceItemIndex); s.writeS32(20, m_deviceConfigs.size()); @@ -129,13 +137,22 @@ bool Preset::deserialize(const QByteArray& data) d.readBlob(5, &m_spectrumConfig); d.readBool(6, &tmpBool, true); d.readS32(7, &tmp, PresetSource); - d.readBool(8, &m_showSpectrum, true); m_presetType = tmp < (int) PresetSource ? PresetSource : tmp > (int) PresetMIMO ? PresetMIMO : (PresetType) tmp; if (m_presetType != PresetMIMO) { m_presetType = tmpBool ? PresetSource : PresetSink; } + d.readBool(8, &m_showSpectrum, true); + d.readBlob(9, &m_spectrumGeometry); + d.readS32(10, &m_spectrumWorkspaceIndex, 0); + d.readBlob(11, &m_deviceGeometry); + d.readS32(12, &m_deviceWorkspaceIndex, 0); + d.readString(13, &m_selectedDevice.m_deviceId); + d.readString(14, &m_selectedDevice.m_deviceSerial); + d.readS32(15, &m_selectedDevice.m_deviceSequence); + d.readS32(16, &m_selectedDevice.m_deviceItemIndex); + // qDebug("Preset::deserialize: m_group: %s mode: %s m_description: %s m_centerFrequency: %llu", // qPrintable(m_group), // m_sourcePreset ? "Rx" : "Tx", @@ -205,7 +222,7 @@ void Preset::addOrUpdateDeviceConfig(const QString& sourceId, int sourceSequence, const QByteArray& config) { - DeviceeConfigs::iterator it = m_deviceConfigs.begin(); + DeviceConfigs::iterator it = m_deviceConfigs.begin(); for (; it != m_deviceConfigs.end(); ++it) { @@ -243,7 +260,7 @@ const QByteArray* Preset::findDeviceConfig( const QString& deviceSerial, int deviceSequence) const { - DeviceeConfigs::const_iterator it = m_deviceConfigs.begin(); + DeviceConfigs::const_iterator it = m_deviceConfigs.begin(); for (; it != m_deviceConfigs.end(); ++it) { @@ -254,46 +271,46 @@ const QByteArray* Preset::findDeviceConfig( return nullptr; } - +//_samplingDeviceId, m_samplingDeviceSerial, m_samplingDeviceSequence const QByteArray* Preset::findBestDeviceConfig( - const QString& sourceId, - const QString& sourceSerial, - int sourceSequence) const + const QString& deviceId, + const QString& deviceSerial, + int deviceSequence) const { // Special case for SoapySDR based on serial (driver name) - if (sourceId == "sdrangel.samplesource.soapysdrinput") { - return findBestDeviceConfigSoapy(sourceId, sourceSerial); - } else if (sourceId == "sdrangel.samplesource.soapysdroutput") { - return findBestDeviceConfigSoapy(sourceId, sourceSerial); + if (deviceId == "sdrangel.samplesource.soapysdrinput") { + return findBestDeviceConfigSoapy(deviceId, deviceSerial); + } else if (deviceId == "sdrangel.samplesource.soapysdroutput") { + return findBestDeviceConfigSoapy(deviceId, deviceSerial); } - DeviceeConfigs::const_iterator it = m_deviceConfigs.begin(); - DeviceeConfigs::const_iterator itFirstOfKind = m_deviceConfigs.end(); - DeviceeConfigs::const_iterator itMatchSequence = m_deviceConfigs.end(); + DeviceConfigs::const_iterator it = m_deviceConfigs.begin(); + DeviceConfigs::const_iterator itFirstOfKind = m_deviceConfigs.end(); + DeviceConfigs::const_iterator itMatchSequence = m_deviceConfigs.end(); for (; it != m_deviceConfigs.end(); ++it) { - if (it->m_deviceId == sourceId) + if (it->m_deviceId == deviceId) { if (itFirstOfKind == m_deviceConfigs.end()) { itFirstOfKind = it; } - if (sourceSerial.isNull() || sourceSerial.isEmpty()) + if (deviceSerial.isNull() || deviceSerial.isEmpty()) { - if (it->m_deviceSequence == sourceSequence) + if (it->m_deviceSequence == deviceSequence) { break; } } else { - if (it->m_deviceSerial == sourceSerial) + if (it->m_deviceSerial == deviceSerial) { break; } - else if(it->m_deviceSequence == sourceSequence) + else if(it->m_deviceSequence == deviceSequence) { itMatchSequence = it; } @@ -303,13 +320,13 @@ const QByteArray* Preset::findBestDeviceConfig( if (it == m_deviceConfigs.end()) // no exact match { - if (itMatchSequence != m_deviceConfigs.end()) // match sequence ? + if (itMatchSequence != m_deviceConfigs.end()) // match device type and sequence ? { qDebug("Preset::findBestDeviceConfig: sequence matched: id: %s ser: %s seq: %d", qPrintable(itMatchSequence->m_deviceId), qPrintable(itMatchSequence->m_deviceSerial), itMatchSequence->m_deviceSequence); return &(itMatchSequence->m_config); } - else if (itFirstOfKind != m_deviceConfigs.end()) // match source type ? + else if (itFirstOfKind != m_deviceConfigs.end()) // match just device type ? { qDebug("Preset::findBestDeviceConfig: first of kind matched: id: %s ser: %s seq: %d", qPrintable(itFirstOfKind->m_deviceId), qPrintable(itFirstOfKind->m_deviceSerial), itFirstOfKind->m_deviceSequence); @@ -337,8 +354,8 @@ const QByteArray* Preset::findBestDeviceConfigSoapy(const QString& sourceId, con return 0; // unable to process } - DeviceeConfigs::const_iterator it = m_deviceConfigs.begin(); - DeviceeConfigs::const_iterator itFirstOfKind = m_deviceConfigs.end(); + DeviceConfigs::const_iterator it = m_deviceConfigs.begin(); + DeviceConfigs::const_iterator itFirstOfKind = m_deviceConfigs.end(); for (; it != m_deviceConfigs.end(); ++it) { diff --git a/sdrbase/settings/preset.h b/sdrbase/settings/preset.h index 148fd74d8..edf306d7e 100644 --- a/sdrbase/settings/preset.h +++ b/sdrbase/settings/preset.h @@ -55,7 +55,19 @@ public: m_config(config) { } }; - typedef QList DeviceeConfigs; + typedef QList DeviceConfigs; + + struct SelectedDevice + { + QString m_deviceId; + QString m_deviceSerial; + int m_deviceSequence; + int m_deviceItemIndex; + + SelectedDevice() = default; + SelectedDevice(const SelectedDevice&) = default; + SelectedDevice& operator=(const SelectedDevice&) = default; + }; enum PresetType { @@ -90,6 +102,16 @@ public: void setSpectrumConfig(const QByteArray& data) { m_spectrumConfig = data; } const QByteArray& getSpectrumConfig() const { return m_spectrumConfig; } + void setSpectrumGeometry(const QByteArray& data) { m_spectrumGeometry = data; } + const QByteArray& getSpectrumGeometry() const { return m_spectrumGeometry; } + void setSpectrumWorkspaceIndex(int workspaceIndex) { m_spectrumWorkspaceIndex = workspaceIndex; } + int getSpectrumWorkspaceIndex() const { return m_spectrumWorkspaceIndex; } + void setSelectedDevice(const SelectedDevice& selectedDevice) { m_selectedDevice = selectedDevice; } + SelectedDevice getSelectedDevice() const { return m_selectedDevice; } + void setDeviceGeometry(const QByteArray& data) { m_deviceGeometry = data; } + const QByteArray& getDeviceGeometry() const { return m_deviceGeometry; } + void setDeviceWorkspaceIndex(int workspaceIndex) { m_deviceWorkspaceIndex = workspaceIndex; } + int getDeviceWorkspaceIndex() const { return m_deviceWorkspaceIndex; } bool hasDCOffsetCorrection() const { return m_dcOffsetCorrection; } void setDCOffsetCorrection(bool dcOffsetCorrection) { m_dcOffsetCorrection = dcOffsetCorrection; } @@ -154,6 +176,11 @@ protected: // general configuration QByteArray m_spectrumConfig; + QByteArray m_spectrumGeometry; + int m_spectrumWorkspaceIndex; + QByteArray m_deviceGeometry; + int m_deviceWorkspaceIndex; + SelectedDevice m_selectedDevice; // dc offset and i/q imbalance correction TODO: move it into the source data bool m_dcOffsetCorrection; @@ -163,14 +190,14 @@ protected: ChannelConfigs m_channelConfigs; // devices and configurations - DeviceeConfigs m_deviceConfigs; + DeviceConfigs m_deviceConfigs; // screen and dock layout bool m_showSpectrum; QByteArray m_layout; private: - const QByteArray* findBestDeviceConfigSoapy(const QString& sourceId, const QString& deviceSerial) const; + const QByteArray* findBestDeviceConfigSoapy(const QString& deviceId, const QString& deviceSerial) const; }; Q_DECLARE_METATYPE(const Preset*) diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 357850af4..57a90f961 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -91,6 +91,8 @@ set(sdrgui_SOURCES feature/featuregui.cpp feature/featureuiset.cpp + mainspectrum/mainspectrumgui.cpp + soapygui/discreterangegui.cpp soapygui/intervalrangegui.cpp soapygui/itemsettinggui.cpp @@ -192,6 +194,8 @@ set(sdrgui_HEADERS feature/featuregui.h feature/featureuiset.h + mainspectrum/mainspectrumgui.h + soapygui/discreterangegui.h soapygui/intervalrangegui.h soapygui/itemsettinggui.h diff --git a/sdrgui/device/devicegui.cpp b/sdrgui/device/devicegui.cpp index c1c1b1727..28715039c 100644 --- a/sdrgui/device/devicegui.cpp +++ b/sdrgui/device/devicegui.cpp @@ -62,14 +62,22 @@ DeviceGUI::DeviceGUI(QWidget *parent) : m_addChannelsButton = new QPushButton(); m_addChannelsButton->setFixedSize(20, 20); - QIcon addChannelsIcon(":/create.png"); + QIcon addChannelsIcon(":/channels_add.png"); m_addChannelsButton->setIcon(addChannelsIcon); m_addChannelsButton->setToolTip("Add channels"); + m_deviceSetPresetsButton = new QPushButton(); + m_deviceSetPresetsButton->setFixedSize(20, 20); + QIcon deviceSetPresetsIcon(":/star.png"); + m_deviceSetPresetsButton->setIcon(deviceSetPresetsIcon); + m_deviceSetPresetsButton->setToolTip("Device set presets"); + m_titleLabel = new QLabel(); m_titleLabel->setText("Device"); m_titleLabel->setToolTip("Device identification"); m_titleLabel->setFixedHeight(20); + m_titleLabel->setMinimumWidth(20); + m_titleLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed); m_helpButton = new QPushButton(); m_helpButton->setFixedSize(20, 20); @@ -98,8 +106,22 @@ DeviceGUI::DeviceGUI(QWidget *parent) : m_statusLabel = new QLabel(); // m_statusLabel->setText("OK"); // for future use m_statusLabel->setFixedHeight(20); + m_statusLabel->setMinimumWidth(20); + m_statusLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed); m_statusLabel->setToolTip("Device status"); + m_showSpectrumButton = new QPushButton(); + m_showSpectrumButton->setFixedSize(20, 20); + QIcon showSpectrumIcon(":/dsb.png"); + m_showSpectrumButton->setIcon(showSpectrumIcon); + m_showSpectrumButton->setToolTip("Show main spectrum"); + + m_showAllChannelsButton = new QPushButton(); + m_showAllChannelsButton->setFixedSize(20, 20); + QIcon showAllChannelsIcon(":/channels.png"); + m_showAllChannelsButton->setIcon(showAllChannelsIcon); + m_showAllChannelsButton->setToolTip("Show all channels"); + m_layouts = new QVBoxLayout(); m_layouts->setContentsMargins(0, 4, 0, 4); m_layouts->setSpacing(2); @@ -110,14 +132,16 @@ DeviceGUI::DeviceGUI(QWidget *parent) : m_topLayout->addWidget(m_changeDeviceButton); m_topLayout->addWidget(m_reloadDeviceButton); m_topLayout->addWidget(m_addChannelsButton); + m_topLayout->addWidget(m_deviceSetPresetsButton); m_topLayout->addWidget(m_titleLabel); - m_topLayout->addStretch(1); + // m_topLayout->addStretch(1); m_topLayout->addWidget(m_helpButton); m_topLayout->addWidget(m_moveButton); m_topLayout->addWidget(m_shrinkButton); m_topLayout->addWidget(m_closeButton); m_sizeGripTopRight = new QSizeGrip(this); m_sizeGripTopRight->setStyleSheet("QSizeGrip { background-color: rgb(128, 128, 128); width: 10px; height: 10px; }"); + m_sizeGripTopRight->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_topLayout->addWidget(m_sizeGripTopRight, 0, Qt::AlignTop | Qt::AlignRight); m_centerLayout = new QHBoxLayout(); @@ -126,10 +150,13 @@ DeviceGUI::DeviceGUI(QWidget *parent) : m_bottomLayout = new QHBoxLayout(); m_bottomLayout->setContentsMargins(0, 0, 0, 0); + m_bottomLayout->addWidget(m_showSpectrumButton); + m_bottomLayout->addWidget(m_showAllChannelsButton); m_bottomLayout->addWidget(m_statusLabel); m_sizeGripBottomRight = new QSizeGrip(this); m_sizeGripBottomRight->setStyleSheet("QSizeGrip { background-color: rgb(128, 128, 128); width: 10px; height: 10px; }"); - m_bottomLayout->addStretch(1); + m_sizeGripBottomRight->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + // m_bottomLayout->addStretch(1); m_bottomLayout->addWidget(m_sizeGripBottomRight, 0, Qt::AlignBottom | Qt::AlignRight); m_layouts->addLayout(m_topLayout); @@ -158,12 +185,15 @@ DeviceGUI::~DeviceGUI() delete m_sizeGripTopRight; delete m_topLayout; delete m_layouts; + delete m_showAllChannelsButton; + delete m_showSpectrumButton; delete m_statusLabel; delete m_closeButton; delete m_shrinkButton; delete m_moveButton; delete m_helpButton; delete m_titleLabel; + delete m_deviceSetPresetsButton; delete m_addChannelsButton; delete m_reloadDeviceButton; delete m_changeDeviceButton; diff --git a/sdrgui/device/devicegui.h b/sdrgui/device/devicegui.h index e6a947c93..a4167d704 100644 --- a/sdrgui/device/devicegui.h +++ b/sdrgui/device/devicegui.h @@ -101,11 +101,14 @@ private: QPushButton *m_changeDeviceButton; QPushButton *m_reloadDeviceButton; QPushButton *m_addChannelsButton; + QPushButton *m_deviceSetPresetsButton; QLabel *m_titleLabel; QPushButton *m_helpButton; QPushButton *m_moveButton; QPushButton *m_shrinkButton; QPushButton *m_closeButton; + QPushButton *m_showSpectrumButton; + QPushButton *m_showAllChannelsButton; QLabel *m_statusLabel; QVBoxLayout *m_layouts; QHBoxLayout *m_topLayout; diff --git a/sdrgui/device/deviceuiset.cpp b/sdrgui/device/deviceuiset.cpp index cb628124c..3570517df 100644 --- a/sdrgui/device/deviceuiset.cpp +++ b/sdrgui/device/deviceuiset.cpp @@ -25,31 +25,35 @@ #include "gui/glspectrum.h" #include "gui/glspectrumgui.h" #include "gui/channelwindow.h" +#include "gui/workspace.h" #include "device/devicegui.h" #include "device/deviceset.h" +#include "device/deviceapi.h" #include "plugin/pluginapi.h" #include "plugin/plugininterface.h" #include "channel/channelutils.h" #include "channel/channelapi.h" #include "channel/channelgui.h" +#include "mainspectrum/mainspectrumgui.h" #include "settings/preset.h" #include "deviceuiset.h" -DeviceUISet::DeviceUISet(int tabIndex, DeviceSet *deviceSet) +DeviceUISet::DeviceUISet(int deviceSetIndex, DeviceSet *deviceSet) { m_spectrum = new GLSpectrum; m_spectrumVis = deviceSet->m_spectrumVis; m_spectrumVis->setGLSpectrum(m_spectrum); m_spectrumGUI = new GLSpectrumGUI; m_spectrumGUI->setBuddies(m_spectrumVis, m_spectrum); + m_mainSpectrumGUI = new MainSpectrumGUI(m_spectrum, m_spectrumGUI); m_channelWindow = new ChannelWindow; m_deviceAPI = nullptr; m_deviceGUI = nullptr; m_deviceSourceEngine = nullptr; m_deviceSinkEngine = nullptr; m_deviceMIMOEngine = nullptr; - m_deviceTabIndex = tabIndex; + m_deviceSetIndex = deviceSetIndex; m_deviceSet = deviceSet; m_nbAvailableRxChannels = 0; // updated at enumeration for UI selector m_nbAvailableTxChannels = 0; // updated at enumeration for UI selector @@ -65,8 +69,9 @@ DeviceUISet::DeviceUISet(int tabIndex, DeviceSet *deviceSet) DeviceUISet::~DeviceUISet() { delete m_channelWindow; - delete m_spectrumGUI; - delete m_spectrum; + delete m_mainSpectrumGUI; + // delete m_spectrumGUI; // done above + // delete m_spectrum; } void DeviceUISet::setSpectrumScalingFactor(float scalef) @@ -156,6 +161,60 @@ ChannelAPI *DeviceUISet::getChannelAt(int channelIndex) return m_deviceSet->getChannelAt(channelIndex); } +void DeviceUISet::loadDeviceSetSettings( + const Preset* preset, + PluginAPI *pluginAPI, + QList *workspaces, + Workspace *currentWorkspace +) +{ + (void) workspaces; // TODO: use for channels + (void) currentWorkspace; // TODO: use for channels + + m_spectrumGUI->deserialize(preset->getSpectrumConfig()); + m_deviceAPI->loadSamplingDeviceSettings(preset); + + if (m_deviceSourceEngine) { // source device + loadRxChannelSettings(preset, pluginAPI); + } else if (m_deviceSinkEngine) { // sink device + loadTxChannelSettings(preset, pluginAPI); + } else if (m_deviceMIMOEngine) { // MIMO device + loadMIMOChannelSettings(preset, pluginAPI); + } +} + +void DeviceUISet::saveDeviceSetSettings(Preset* preset) const +{ + preset->setSpectrumConfig(m_spectrumGUI->serialize()); + preset->setSpectrumWorkspaceIndex(m_mainSpectrumGUI->getWorkspaceIndex()); + preset->setSpectrumGeometry(m_mainSpectrumGUI->saveGeometry()); + preset->setSelectedDevice(Preset::SelectedDevice{ + m_deviceAPI->getSamplingDeviceId(), + m_deviceAPI->getSamplingDeviceSerial(), + (int) m_deviceAPI->getSamplingDeviceSequence(), + (int) m_deviceAPI->getDeviceItemIndex() + }); + preset->clearChannels(); + + if (m_deviceSourceEngine) // source device + { + preset->setSourcePreset(); + saveRxChannelSettings(preset); + } + else if (m_deviceSinkEngine) // sink device + { + preset->setSinkPreset(); + saveTxChannelSettings(preset); + } + else if (m_deviceMIMOEngine) // MIMO device + { + preset->setMIMOPreset(); + saveMIMOChannelSettings(preset); + } + + m_deviceAPI->saveSamplingDeviceSettings(preset); +} + void DeviceUISet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginAPI) { if (preset->isSourcePreset()) @@ -224,7 +283,7 @@ void DeviceUISet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginA } } -void DeviceUISet::saveRxChannelSettings(Preset *preset) +void DeviceUISet::saveRxChannelSettings(Preset *preset) const { if (preset->isSourcePreset()) { @@ -308,7 +367,7 @@ void DeviceUISet::loadTxChannelSettings(const Preset *preset, PluginAPI *pluginA } -void DeviceUISet::saveTxChannelSettings(Preset *preset) +void DeviceUISet::saveTxChannelSettings(Preset *preset) const { if (preset->isSinkPreset()) { @@ -392,7 +451,7 @@ void DeviceUISet::loadMIMOChannelSettings(const Preset *preset, PluginAPI *plugi } } -void DeviceUISet::saveMIMOChannelSettings(Preset *preset) +void DeviceUISet::saveMIMOChannelSettings(Preset *preset) const { if (preset->isMIMOPreset()) { diff --git a/sdrgui/device/deviceuiset.h b/sdrgui/device/deviceuiset.h index 4965b431b..5d133d4b7 100644 --- a/sdrgui/device/deviceuiset.h +++ b/sdrgui/device/deviceuiset.h @@ -27,6 +27,7 @@ class SpectrumVis; class GLSpectrum; class GLSpectrumGUI; +class MainSpectrumGUI; class ChannelWindow; class DeviceAPI; class DeviceSet; @@ -39,6 +40,7 @@ class DeviceGUI; class ChannelAPI; class ChannelGUI; class Preset; +class Workspace; namespace SWGSDRangel { class SWGGLSpectrum; @@ -53,6 +55,7 @@ public: SpectrumVis *m_spectrumVis; GLSpectrum *m_spectrum; GLSpectrumGUI *m_spectrumGUI; + MainSpectrumGUI *m_mainSpectrumGUI; ChannelWindow *m_channelWindow; DeviceAPI *m_deviceAPI; DeviceGUI *m_deviceGUI; @@ -60,8 +63,12 @@ public: DSPDeviceSinkEngine *m_deviceSinkEngine; DSPDeviceMIMOEngine *m_deviceMIMOEngine; QByteArray m_mainWindowState; + QString m_selectedDeviceId; + QString m_selectedDeviceSerial; + int m_selectedDeviceSequence; + int m_selectedDeviceItemImdex; - DeviceUISet(int tabIndex, DeviceSet *deviceSet); + DeviceUISet(int deviceSetIndex, DeviceSet *deviceSet); ~DeviceUISet(); GLSpectrum *getSpectrum() { return m_spectrum; } //!< Direct spectrum getter @@ -73,12 +80,15 @@ public: void freeChannels(); void deleteChannel(int channelIndex); ChannelAPI *getChannelAt(int channelIndex); - void loadRxChannelSettings(const Preset* preset, PluginAPI *pluginAPI); - void saveRxChannelSettings(Preset* preset); - void loadTxChannelSettings(const Preset* preset, PluginAPI *pluginAPI); - void saveTxChannelSettings(Preset* preset); - void loadMIMOChannelSettings(const Preset* preset, PluginAPI *pluginAPI); - void saveMIMOChannelSettings(Preset* preset); + + void loadDeviceSetSettings( + const Preset* preset, + PluginAPI *pluginAPI, + QList *workspaces, + Workspace *currentWorkspace + ); + void saveDeviceSetSettings(Preset* preset) const; + void registerRxChannelInstance(ChannelAPI *channelAPI, ChannelGUI* channelGUI); void registerTxChannelInstance(ChannelAPI *channelAPI, ChannelGUI* channelGUI); void registerChannelInstance(ChannelAPI *channelAPI, ChannelGUI* channelGUI); @@ -129,12 +139,19 @@ private: // ChannelInstanceRegistrations m_rxChannelInstanceRegistrations; // ChannelInstanceRegistrations m_txChannelInstanceRegistrations; ChannelInstanceRegistrations m_channelInstanceRegistrations; - int m_deviceTabIndex; + int m_deviceSetIndex; DeviceSet *m_deviceSet; int m_nbAvailableRxChannels; //!< Number of Rx channels available for selection int m_nbAvailableTxChannels; //!< Number of Tx channels available for selection int m_nbAvailableMIMOChannels; //!< Number of MIMO channels available for selection + void loadRxChannelSettings(const Preset* preset, PluginAPI *pluginAPI); + void loadTxChannelSettings(const Preset* preset, PluginAPI *pluginAPI); + void loadMIMOChannelSettings(const Preset* preset, PluginAPI *pluginAPI); + void saveRxChannelSettings(Preset* preset) const; + void saveTxChannelSettings(Preset* preset) const; + void saveMIMOChannelSettings(Preset* preset) const; + private slots: void handleChannelGUIClosing(ChannelGUI* channelGUI); }; diff --git a/sdrgui/feature/featuregui.cpp b/sdrgui/feature/featuregui.cpp index 41638097e..248be215b 100644 --- a/sdrgui/feature/featuregui.cpp +++ b/sdrgui/feature/featuregui.cpp @@ -54,6 +54,8 @@ FeatureGUI::FeatureGUI(QWidget *parent) : m_titleLabel->setText("Feature"); m_titleLabel->setToolTip("Feature name"); m_titleLabel->setFixedHeight(20); + m_titleLabel->setMinimumWidth(20); + m_titleLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed); m_helpButton = new QPushButton(); m_helpButton->setFixedSize(20, 20); @@ -82,6 +84,8 @@ FeatureGUI::FeatureGUI(QWidget *parent) : m_statusLabel = new QLabel(); // m_statusLabel->setText("OK"); // for future use m_statusLabel->setFixedHeight(20); + m_statusLabel->setMinimumWidth(20); + m_statusLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed); m_statusLabel->setToolTip("Feature status"); m_layouts = new QVBoxLayout(); @@ -93,13 +97,14 @@ FeatureGUI::FeatureGUI(QWidget *parent) : m_topLayout->addWidget(m_indexLabel); m_topLayout->addWidget(m_settingsButton); m_topLayout->addWidget(m_titleLabel); - m_topLayout->addStretch(1); + // m_topLayout->addStretch(1); m_topLayout->addWidget(m_helpButton); m_topLayout->addWidget(m_moveButton); m_topLayout->addWidget(m_shrinkButton); m_topLayout->addWidget(m_closeButton); m_sizeGripTopRight = new QSizeGrip(this); m_sizeGripTopRight->setStyleSheet("QSizeGrip { background-color: rgb(128, 128, 128); width: 10px; height: 10px; }"); + m_sizeGripTopRight->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_topLayout->addWidget(m_sizeGripTopRight, 0, Qt::AlignTop | Qt::AlignRight); m_centerLayout = new QHBoxLayout(); @@ -110,7 +115,8 @@ FeatureGUI::FeatureGUI(QWidget *parent) : m_bottomLayout->addWidget(m_statusLabel); m_sizeGripBottomRight = new QSizeGrip(this); m_sizeGripBottomRight->setStyleSheet("QSizeGrip { background-color: rgb(128, 128, 128); width: 10px; height: 10px; }"); - m_bottomLayout->addStretch(1); + m_sizeGripBottomRight->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + // m_bottomLayout->addStretch(1); m_bottomLayout->addWidget(m_sizeGripBottomRight, 0, Qt::AlignBottom | Qt::AlignRight); m_layouts->addLayout(m_topLayout); diff --git a/sdrgui/feature/featureuiset.cpp b/sdrgui/feature/featureuiset.cpp index e09d2d0fb..3bf8c7453 100644 --- a/sdrgui/feature/featureuiset.cpp +++ b/sdrgui/feature/featureuiset.cpp @@ -134,7 +134,8 @@ void FeatureUISet::loadFeatureSetSettings( const FeatureSetPreset *preset, PluginAPI *pluginAPI, WebAPIAdapterInterface *apiAdapter, - Workspace *workspace + QList *workspaces, + Workspace *currentWorkspace ) { qDebug("FeatureUISet::loadFeatureSetSettings: Loading preset [%s | %s]", @@ -161,58 +162,6 @@ void FeatureUISet::loadFeatureSetSettings( qDebug("FeatureUISet::loadFeatureSetSettings: %d feature(s) in preset", preset->getFeatureCount()); - for (int i = 0; i < preset->getFeatureCount(); i++) - { - const FeatureSetPreset::FeatureConfig& featureConfig = preset->getFeatureConfig(i); - FeatureGUI *featureGUI = nullptr; - Feature *feature = nullptr; - - // create feature instance - - for(int i = 0; i < featureRegistrations->count(); i++) - { - if (FeatureUtils::compareFeatureURIs((*featureRegistrations)[i].m_featureIdURI, featureConfig.m_featureIdURI)) - { - qDebug("FeatureUISet::loadFeatureSetSettings: creating new feature [%s] from config [%s]", - qPrintable((*featureRegistrations)[i].m_featureIdURI), - qPrintable(featureConfig.m_featureIdURI) - ); - feature = (*featureRegistrations)[i].m_plugin->createFeature(apiAdapter); - featureGUI = (*featureRegistrations)[i].m_plugin->createFeatureGUI(this, feature); - registerFeatureInstance(featureGUI, feature); - break; - } - } - - if (featureGUI) - { - qDebug("FeatureUISet::loadFeatureSetSettings: deserializing feature [%s]", - qPrintable(featureConfig.m_featureIdURI)); - featureGUI->deserialize(featureConfig.m_config); - - if (workspace) // restore in current workspace - { - featureGUI->setIndex(feature->getIndexInFeatureSet()); - featureGUI->setWorkspaceIndex(workspace->getIndex()); - workspace->addToMdiArea((QMdiSubWindow*) featureGUI); - } - } - } -} - -void FeatureUISet::loadFeatureSetSettings( - const FeatureSetPreset* preset, - PluginAPI *pluginAPI, - WebAPIAdapterInterface *apiAdapter, - QList& workspaces -) -{ - // This method loads from scratch - load from configuration - qDebug("FeatureUISet::loadFeatureSetSettings: %d feature(s) in preset", preset->getFeatureCount()); - - // Available feature plugins - PluginAPI::FeatureRegistrations *featureRegistrations = pluginAPI->getFeatureRegistrations(); - for (int i = 0; i < preset->getFeatureCount(); i++) { const FeatureSetPreset::FeatureConfig& featureConfig = preset->getFeatureConfig(i); @@ -242,8 +191,19 @@ void FeatureUISet::loadFeatureSetSettings( qPrintable(featureConfig.m_featureIdURI)); featureGUI->deserialize(featureConfig.m_config); featureGUI->setIndex(feature->getIndexInFeatureSet()); - workspaces[featureGUI->getWorkspaceIndex()]->addToMdiArea((QMdiSubWindow*) featureGUI); - featureGUI->restoreGeometry(featureGUI->getGeometryBytes()); + int originalWorkspaceIndex = featureGUI->getWorkspaceIndex(); + + if (workspaces && (workspaces->size() > 0) && (originalWorkspaceIndex < workspaces->size())) // restore in original workspace + { + (*workspaces)[originalWorkspaceIndex]->addToMdiArea((QMdiSubWindow*) featureGUI); + featureGUI->restoreGeometry(featureGUI->getGeometryBytes()); + } + else if (currentWorkspace) // restore in current workspace + { + featureGUI->setWorkspaceIndex(currentWorkspace->getIndex()); + currentWorkspace->addToMdiArea((QMdiSubWindow*) featureGUI); + featureGUI->restoreGeometry(featureGUI->getGeometryBytes()); + } } } } diff --git a/sdrgui/feature/featureuiset.h b/sdrgui/feature/featureuiset.h index 372a3cb3e..e557527cf 100644 --- a/sdrgui/feature/featureuiset.h +++ b/sdrgui/feature/featureuiset.h @@ -52,13 +52,8 @@ public: const FeatureSetPreset* preset, PluginAPI *pluginAPI, WebAPIAdapterInterface *apiAdapter, - Workspace *workspace - ); - void loadFeatureSetSettings( - const FeatureSetPreset* preset, - PluginAPI *pluginAPI, - WebAPIAdapterInterface *apiAdapter, - QList& workspaces + QList *workspaces, + Workspace *currentWorkspace ); void saveFeatureSetSettings(FeatureSetPreset* preset); void freeFeatures(); diff --git a/sdrgui/gui/featurepresetsdialog.cpp b/sdrgui/gui/featurepresetsdialog.cpp index 6458a43e1..acd902766 100644 --- a/sdrgui/gui/featurepresetsdialog.cpp +++ b/sdrgui/gui/featurepresetsdialog.cpp @@ -34,6 +34,8 @@ FeaturePresetsDialog::FeaturePresetsDialog(QWidget* parent) : m_featureUISet(nullptr), m_pluginAPI(nullptr), m_apiAdapter(nullptr), + m_currentWorkspace(nullptr), + m_workspaces(nullptr), m_presetLoaded(false) { ui->setupUi(this); @@ -374,7 +376,7 @@ void FeaturePresetsDialog::loadPresetSettings(const FeatureSetPreset* preset) qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); - m_featureUISet->loadFeatureSetSettings(preset, m_pluginAPI, m_apiAdapter, m_workspace); + m_featureUISet->loadFeatureSetSettings(preset, m_pluginAPI, m_apiAdapter, m_workspaces, m_currentWorkspace); m_presetLoaded = true; } diff --git a/sdrgui/gui/featurepresetsdialog.h b/sdrgui/gui/featurepresetsdialog.h index 1addbf404..c5b9c3d2b 100644 --- a/sdrgui/gui/featurepresetsdialog.h +++ b/sdrgui/gui/featurepresetsdialog.h @@ -44,7 +44,8 @@ public: void setFeatureUISet(FeatureUISet *featureUISet) { m_featureUISet = featureUISet; } void setPluginAPI(PluginAPI *pluginAPI) { m_pluginAPI = pluginAPI; } void setWebAPIAdapter(WebAPIAdapterInterface *apiAdapter) { m_apiAdapter = apiAdapter; } - void setWorkspace(Workspace *workspace) { m_workspace = workspace; } + void setCurrentWorkspace(Workspace *workspace) { m_currentWorkspace = workspace; } + void setWorkspaces(QList *workspaces) { m_workspaces = workspaces; } void populateTree(); bool wasPresetLoaded() const { return m_presetLoaded; } @@ -59,7 +60,8 @@ private: FeatureUISet *m_featureUISet; PluginAPI *m_pluginAPI; WebAPIAdapterInterface *m_apiAdapter; - Workspace *m_workspace; + Workspace *m_currentWorkspace; + QList *m_workspaces; bool m_presetLoaded; QTreeWidgetItem* addPresetToTree(const FeatureSetPreset* preset); diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index d9b846398..2c9c1513f 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -61,7 +61,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_displayGrid(true), m_displayGridIntensity(5), m_displayTraceIntensity(50), - m_invertedWaterfall(false), + m_invertedWaterfall(true), m_displayMaxHold(false), m_currentSpectrum(nullptr), m_displayCurrent(false), @@ -69,7 +69,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_rightMargin(0), m_topMargin(0), m_frequencyScaleHeight(0), - m_histogramHeight(20), + m_histogramHeight(80), m_waterfallHeight(0), m_bottomMargin(0), m_waterfallBuffer(nullptr), @@ -93,6 +93,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_calibrationInterpMode(SpectrumSettings::CalibInterpLinear), m_messageQueueToGUI(nullptr) { + setObjectName("GLSpectrum"); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent, true); setAttribute(Qt::WA_NoSystemBackground, true); @@ -100,7 +101,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : setMinimumSize(200, 200); - m_waterfallShare = 0.66; + m_waterfallShare = 0.5; for (int i = 0; i <= 239; i++) { diff --git a/sdrgui/gui/workspace.cpp b/sdrgui/gui/workspace.cpp index 724f10116..a40b82bee 100644 --- a/sdrgui/gui/workspace.cpp +++ b/sdrgui/gui/workspace.cpp @@ -73,13 +73,13 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : m_vline1->setFrameShadow(QFrame::Sunken); m_addFeatureButton = new QPushButton(); - QIcon addFeatureIcon(":/tool.png"); + QIcon addFeatureIcon(":/tool_add.png"); m_addFeatureButton->setIcon(addFeatureIcon); m_addFeatureButton->setToolTip("Add features"); m_addFeatureButton->setFixedSize(20, 20); m_featurePresetsButton = new QPushButton(); - QIcon presetsIcon(":/star.png"); + QIcon presetsIcon(":/tool_star.png"); m_featurePresetsButton->setIcon(presetsIcon); m_featurePresetsButton->setToolTip("Feature presets"); m_featurePresetsButton->setFixedSize(20, 20); diff --git a/sdrgui/mainspectrum/mainspectrumgui.cpp b/sdrgui/mainspectrum/mainspectrumgui.cpp new file mode 100644 index 000000000..04349036b --- /dev/null +++ b/sdrgui/mainspectrum/mainspectrumgui.cpp @@ -0,0 +1,290 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mainwindow.h" +#include "gui/glspectrum.h" +#include "gui/glspectrumgui.h" +#include "gui/workspaceselectiondialog.h" +#include "mainspectrumgui.h" + +MainSpectrumGUI::MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent) : + QMdiSubWindow(parent), + m_spectrum(spectrum), + m_spectrumGUI(spectrumGUI), + m_deviceType(DeviceRx), + m_deviceSetIndex(0), + m_drag(false) +{ + qDebug("MainSpectrumGUI::MainSpectrumGUI: %p", parent); + setWindowFlags(windowFlags() | Qt::FramelessWindowHint); + + m_indexLabel = new QLabel(); + m_indexLabel->setFixedSize(32, 16); + m_indexLabel->setStyleSheet("QLabel { background-color: rgb(128, 128, 128); qproperty-alignment: AlignCenter; }"); + m_indexLabel->setText(tr("X:%1").arg(m_deviceSetIndex)); + m_indexLabel->setToolTip("Device type and set index"); + + m_spacerLabel = new QLabel(); + m_spacerLabel->setFixedWidth(5); + + m_titleLabel = new QLabel(); + m_titleLabel->setText("Device"); + m_titleLabel->setToolTip("Device identification"); + m_titleLabel->setFixedHeight(20); + m_titleLabel->setMinimumWidth(20); + m_titleLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed); + + m_helpButton = new QPushButton(); + m_helpButton->setFixedSize(20, 20); + QIcon helpIcon(":/help.png"); + m_helpButton->setIcon(helpIcon); + m_helpButton->setToolTip("Show spectrum window documentation in browser"); + + m_moveButton = new QPushButton(); + m_moveButton->setFixedSize(20, 20); + QIcon moveIcon(":/exit.png"); + m_moveButton->setIcon(moveIcon); + m_moveButton->setToolTip("Move to workspace"); + + m_shrinkButton = new QPushButton(); + m_shrinkButton->setFixedSize(20, 20); + QIcon shrinkIcon(":/shrink.png"); + m_shrinkButton->setIcon(shrinkIcon); + m_shrinkButton->setToolTip("Adjust window to minimum size"); + + m_hideButton = new QPushButton(); + m_hideButton->setFixedSize(20, 20); + QIcon hideIcon(":/hide.png"); + m_hideButton->setIcon(hideIcon); + m_hideButton->setToolTip("Hide device"); + + m_statusLabel = new QLabel(); + // m_statusLabel->setText("OK"); // for future use + m_statusLabel->setFixedHeight(10); + m_statusLabel->setMinimumWidth(10); + m_statusLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed); + // m_statusLabel->setToolTip("Spectrum status"); + + m_layouts = new QVBoxLayout(); + m_layouts->setContentsMargins(0, 4, 0, 4); + m_layouts->setSpacing(0); + + m_topLayout = new QHBoxLayout(); + m_topLayout->setContentsMargins(0, 0, 0, 0); + m_topLayout->addWidget(m_indexLabel); + m_topLayout->addWidget(m_spacerLabel); + m_topLayout->addWidget(m_titleLabel); + // m_topLayout->addStretch(1); + m_topLayout->addWidget(m_helpButton); + m_topLayout->addWidget(m_moveButton); + m_topLayout->addWidget(m_shrinkButton); + m_topLayout->addWidget(m_hideButton); + m_sizeGripTopRight = new QSizeGrip(this); + m_sizeGripTopRight->setStyleSheet("QSizeGrip { background-color: rgb(128, 128, 128); width: 10px; height: 10px; }"); + m_sizeGripTopRight->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_topLayout->addWidget(m_sizeGripTopRight, 0, Qt::AlignTop | Qt::AlignRight); + + m_spectrumLayout = new QHBoxLayout(); + m_spectrumLayout->addWidget(spectrum); + spectrum->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_spectrumGUILayout = new QHBoxLayout(); + m_spectrumGUILayout->addWidget(spectrumGUI); + + m_bottomLayout = new QHBoxLayout(); + m_bottomLayout->setContentsMargins(0, 0, 0, 0); + m_bottomLayout->addWidget(m_statusLabel); + m_sizeGripBottomRight = new QSizeGrip(this); + m_sizeGripBottomRight->setStyleSheet("QSizeGrip { background-color: rgb(128, 128, 128); width: 10px; height: 10px; }"); + m_sizeGripBottomRight->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + //m_bottomLayout->addStretch(1); + m_bottomLayout->addWidget(m_sizeGripBottomRight, 0, Qt::AlignBottom | Qt::AlignRight); + + m_layouts->addLayout(m_topLayout); + m_layouts->addLayout(m_spectrumLayout); + m_layouts->addLayout(m_spectrumGUILayout); + m_layouts->addLayout(m_bottomLayout); + + QObjectCleanupHandler().add(layout()); + setLayout(m_layouts); + + connect(m_helpButton, SIGNAL(clicked()), this, SLOT(showHelp())); + connect(m_moveButton, SIGNAL(clicked()), this, SLOT(openMoveToWorkspaceDialog())); + connect(m_shrinkButton, SIGNAL(clicked()), this, SLOT(shrinkWindow())); + connect(this, SIGNAL(forceShrink()), this, SLOT(shrinkWindow())); + connect(m_hideButton, SIGNAL(clicked()), this, SLOT(hide())); + connect(this, SIGNAL(forceClose()), this, SLOT(close())); + + shrinkWindow(); +} + +MainSpectrumGUI::~MainSpectrumGUI() +{ + qDebug("MainSpectrumGUI::~MainSpectrumGUI"); + m_spectrumLayout->removeWidget(m_spectrum); + m_spectrumGUILayout->removeWidget(m_spectrumGUI); + delete m_sizeGripBottomRight; + delete m_bottomLayout; + delete m_spectrumGUILayout; + delete m_spectrumLayout; + delete m_sizeGripTopRight; + delete m_topLayout; + delete m_layouts; + delete m_statusLabel; + delete m_hideButton; + delete m_shrinkButton; + delete m_moveButton; + delete m_helpButton; + delete m_titleLabel; + delete m_spacerLabel; + delete m_indexLabel; + qDebug("MainSpectrumGUI::~MainSpectrumGUI: end"); +} + +void MainSpectrumGUI::closeEvent(QCloseEvent *event) +{ + qDebug("MainSpectrumGUI::closeEvent"); + emit closing(); + event->accept(); +} + +void MainSpectrumGUI::mousePressEvent(QMouseEvent* event) +{ + if ((event->button() == Qt::LeftButton) && isOnMovingPad()) + { + m_drag = true; + m_DragPosition = event->globalPos() - pos(); + event->accept(); + } +} + +void MainSpectrumGUI::mouseMoveEvent(QMouseEvent* event) +{ + if ((event->buttons() & Qt::LeftButton) && isOnMovingPad()) + { + move(event->globalPos() - m_DragPosition); + event->accept(); + } +} + +void MainSpectrumGUI::showHelp() +{ + if (m_helpURL.isEmpty()) { + return; + } + + QString url; + + if (m_helpURL.startsWith("http")) { + url = m_helpURL; + } else { + url = QString("https://github.com/f4exb/sdrangel/blob/master/%1").arg(m_helpURL); // Something like "plugins/channelrx/chanalyzer/readme.md" + } + + QDesktopServices::openUrl(QUrl(url)); +} + +void MainSpectrumGUI::openMoveToWorkspaceDialog() +{ + int numberOfWorkspaces = MainWindow::getInstance()->getNumberOfWorkspaces(); + WorkspaceSelectionDialog dialog(numberOfWorkspaces, this); + dialog.exec(); + + if (dialog.hasChanged()) { + emit moveToWorkspace(dialog.getSelectedIndex()); + } +} + +void MainSpectrumGUI::shrinkWindow() +{ + qDebug("MainSpectrumGUI::shrinkWindow"); + adjustSize(); + resize(width(), 360); +} + +void MainSpectrumGUI::setTitle(const QString& title) +{ + m_titleLabel->setText(title); +} + +QString MainSpectrumGUI::getTitle() const +{ + return m_titleLabel->text(); +} + +bool MainSpectrumGUI::isOnMovingPad() +{ + return m_indexLabel->underMouse() || + m_spacerLabel->underMouse() || + m_titleLabel->underMouse() || + m_statusLabel->underMouse(); +} + +void MainSpectrumGUI::setIndex(int index) +{ + m_deviceSetIndex = index; + m_indexLabel->setText(tr("%1:%2").arg(getDeviceTypeTag()).arg(m_deviceSetIndex)); +} + +void MainSpectrumGUI::setDeviceType(DeviceType type) +{ + m_deviceType = type; + m_indexLabel->setStyleSheet(tr("QLabel { background-color: %1; qproperty-alignment: AlignCenter; }").arg(getDeviceTypeColor())); +} + +void MainSpectrumGUI::setToolTip(const QString& tooltip) +{ + m_titleLabel->setToolTip(tooltip); +} + +QString MainSpectrumGUI::getDeviceTypeColor() +{ + switch(m_deviceType) + { + case DeviceRx: + return "rgb(0, 128, 0)"; + case DeviceTx: + return "rgb(204, 0, 0)"; + case DeviceMIMO: + return "rgb(0, 0, 192)"; + default: + return "rgb(128, 128, 128)"; + } +} + +QString MainSpectrumGUI::getDeviceTypeTag() +{ + switch(m_deviceType) + { + case DeviceRx: + return "R"; + case DeviceTx: + return "T"; + case DeviceMIMO: + return "M"; + default: + return "X"; + } +} diff --git a/sdrgui/mainspectrum/mainspectrumgui.h b/sdrgui/mainspectrum/mainspectrumgui.h new file mode 100644 index 000000000..4a03ba983 --- /dev/null +++ b/sdrgui/mainspectrum/mainspectrumgui.h @@ -0,0 +1,110 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRGUI_MAINSPECTRUM_MAINSPECTRUMGUIGUI_H_ +#define SDRGUI_MAINSPECTRUM_MAINSPECTRUMGUIGUI_H_ + +#include +#include + +#include "util/messagequeue.h" +#include "export.h" + +class GLSpectrum; +class GLSpectrumGUI; +class QLabel; +class QPushButton; +class QVBoxLayout; +class QHBoxLayout; +class QSizeGrip; + +class SDRGUI_API MainSpectrumGUI : public QMdiSubWindow +{ + Q_OBJECT +public: + enum DeviceType + { + DeviceRx, + DeviceTx, + DeviceMIMO + }; + + MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent = nullptr); + virtual ~MainSpectrumGUI(); + + void setDeviceType(DeviceType type); + DeviceType getDeviceType() const { return m_deviceType; } + void setTitle(const QString& title); + QString getTitle() const; + void setToolTip(const QString& tooltip); + void setIndex(int index); + int getIndex() const { return m_deviceSetIndex; } + void setWorkspaceIndex(int index) { m_workspaceIndex = index; } + int getWorkspaceIndex() const { return m_workspaceIndex; } + void setGeometryBytes(const QByteArray& blob) { m_geometryBytes = blob; } + const QByteArray& getGeometryBytes() const { return m_geometryBytes; } + +private: + GLSpectrum *m_spectrum; + GLSpectrumGUI *m_spectrumGUI; + int m_workspaceIndex; + QByteArray m_geometryBytes; + DeviceType m_deviceType; + int m_deviceSetIndex; + QString m_deviceTitle; + QString m_deviceTooltip; + QString m_helpURL; + + QLabel *m_indexLabel; + QLabel *m_spacerLabel; + QLabel *m_titleLabel; + QPushButton *m_helpButton; + QPushButton *m_moveButton; + QPushButton *m_shrinkButton; + QPushButton *m_hideButton; + QLabel *m_statusLabel; + QVBoxLayout *m_layouts; + QHBoxLayout *m_topLayout; + QHBoxLayout *m_spectrumLayout; + QHBoxLayout *m_spectrumGUILayout; + QHBoxLayout *m_bottomLayout; + QSizeGrip *m_sizeGripTopRight; + QSizeGrip *m_sizeGripBottomRight; + bool m_drag; + QPoint m_DragPosition; + + void closeEvent(QCloseEvent *event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + bool isOnMovingPad(); + QString getDeviceTypeColor(); + QString getDeviceTypeTag(); + +private slots: + void showHelp(); + void openMoveToWorkspaceDialog(); + void shrinkWindow(); + +signals: + void forceClose(); + void closing(); + void moveToWorkspace(int workspaceIndex); + void forceShrink(); +}; + + +#endif // SDRGUI_MAINSPECTRUM_MAINSPECTRUMGUIGUI_H_ diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index 76d0881ee..3b9d3f654 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -47,6 +47,7 @@ #include "feature/featureset.h" #include "feature/feature.h" #include "feature/featuregui.h" +#include "mainspectrum/mainspectrumgui.h" #include "commands/commandkeyreceiver.h" #include "gui/indicator.h" #include "gui/presetitem.h" @@ -261,7 +262,7 @@ MainWindow::MainWindow(qtwebapp::LoggerWithFile *logger, const MainParser& parse splash->showStatusMessage("load current configuration...", Qt::white); qDebug() << "MainWindow::MainWindow: load current configuration..."; - // loadPresetSettings(m_mainCore->m_settings.getWorkingPreset(), 0); + // loadDeviceSetPresetSettings(m_mainCore->m_settings.getWorkingPreset(), 0); m_apiAdapter = new WebAPIAdapter(); // loadFeatureSetPresetSettings(m_mainCore->m_settings.getWorkingFeatureSetPreset(), 0); loadConfiguration(m_mainCore->m_settings.getWorkingConfiguration()); @@ -389,16 +390,27 @@ void MainWindow::sampleSourceAdd(Workspace *workspace, int deviceIndex) deviceIndex = DeviceEnumerator::instance()->getFileInputDeviceIndex(); } - sampleSourceImplement(deviceSetIndex, deviceIndex, deviceAPI, m_deviceUIs.back(), workspace); + sampleSourceCreate(deviceSetIndex, deviceIndex, deviceAPI, m_deviceUIs.back()); + m_deviceUIs.back()->m_deviceGUI->setWorkspaceIndex(workspace->getIndex()); + m_deviceUIs.back()->m_mainSpectrumGUI->setWorkspaceIndex(workspace->getIndex()); + + QObject::connect( + m_deviceUIs.back()->m_mainSpectrumGUI, + &MainSpectrumGUI::moveToWorkspace, + this, + [=](int wsIndexDest){ this->mainSpectrumMove(m_deviceUIs.back()->m_mainSpectrumGUI, wsIndexDest); } + ); + + workspace->addToMdiArea(m_deviceUIs.back()->m_deviceGUI); + workspace->addToMdiArea(m_deviceUIs.back()->m_mainSpectrumGUI); emit m_mainCore->deviceSetAdded(deviceSetIndex, deviceAPI); } -void MainWindow::sampleSourceImplement( +void MainWindow::sampleSourceCreate( int deviceSetIndex, int deviceIndex, DeviceAPI *deviceAPI, - DeviceUISet *deviceUISet, - Workspace *workspace + DeviceUISet *deviceUISet ) { int selectedDeviceIndex = deviceIndex; @@ -506,15 +518,23 @@ void MainWindow::sampleSourceImplement( ); deviceAPI->getSampleSource()->setMessageQueueToGUI(deviceGUI->getInputMessageQueue()); deviceUISet->m_deviceGUI = deviceGUI; + const PluginInterface::SamplingDevice *selectedDevice = DeviceEnumerator::instance()->getRxSamplingDevice(selectedDeviceIndex); + deviceUISet->m_selectedDeviceId = selectedDevice->id; + deviceUISet->m_selectedDeviceSerial = selectedDevice->serial; + deviceUISet->m_selectedDeviceSequence = selectedDevice->sequence; + deviceUISet->m_selectedDeviceItemImdex = selectedDevice->deviceItemIndex; deviceUISet->m_deviceAPI->getSampleSource()->init(); // Finalize GUI setup and add it to workspace MDI deviceGUI->setDeviceType(DeviceGUI::DeviceRx); deviceGUI->setIndex(deviceSetIndex); - deviceGUI->setWorkspaceIndex(workspace->getIndex()); deviceGUI->setToolTip(samplingDevice->displayedName); deviceGUI->setTitle(samplingDevice->displayedName.split(" ")[0]); deviceGUI->setCurrentDeviceIndex(selectedDeviceIndex); - workspace->addToMdiArea((QMdiSubWindow*) deviceGUI); + MainSpectrumGUI *mainSpectrumGUI = deviceUISet->m_mainSpectrumGUI; + mainSpectrumGUI->setDeviceType(MainSpectrumGUI::DeviceRx); + mainSpectrumGUI->setIndex(deviceSetIndex); + mainSpectrumGUI->setToolTip(samplingDevice->displayedName); + mainSpectrumGUI->setTitle(samplingDevice->displayedName.split(" ")[0]); } void MainWindow::sampleSinkAdd(Workspace *workspace, int deviceIndex) @@ -552,16 +572,27 @@ void MainWindow::sampleSinkAdd(Workspace *workspace, int deviceIndex) deviceIndex = DeviceEnumerator::instance()->getFileOutputDeviceIndex(); // create a file output by default } - sampleSinkImplement(deviceSetIndex, deviceIndex, deviceAPI, m_deviceUIs.back(), workspace); + sampleSinkCreate(deviceSetIndex, deviceIndex, deviceAPI, m_deviceUIs.back()); + m_deviceUIs.back()->m_deviceGUI->setWorkspaceIndex(workspace->getIndex()); + m_deviceUIs.back()->m_mainSpectrumGUI->setWorkspaceIndex(workspace->getIndex()); + + QObject::connect( + m_deviceUIs.back()->m_mainSpectrumGUI, + &MainSpectrumGUI::moveToWorkspace, + this, + [=](int wsIndexDest){ this->mainSpectrumMove(m_deviceUIs.back()->m_mainSpectrumGUI, wsIndexDest); } + ); + + workspace->addToMdiArea(m_deviceUIs.back()->m_deviceGUI); + workspace->addToMdiArea(m_deviceUIs.back()->m_mainSpectrumGUI); emit m_mainCore->deviceSetAdded(deviceSetIndex, deviceAPI); } -void MainWindow::sampleSinkImplement( +void MainWindow::sampleSinkCreate( int deviceSetIndex, int deviceIndex, DeviceAPI *deviceAPI, - DeviceUISet *deviceUISet, - Workspace *workspace + DeviceUISet *deviceUISet ) { int selectedDeviceIndex = deviceIndex; @@ -669,15 +700,23 @@ void MainWindow::sampleSinkImplement( ); deviceAPI->getSampleSink()->setMessageQueueToGUI(deviceGUI->getInputMessageQueue()); deviceUISet->m_deviceGUI = deviceGUI; + const PluginInterface::SamplingDevice *selectedDevice = DeviceEnumerator::instance()->getRxSamplingDevice(selectedDeviceIndex); + deviceUISet->m_selectedDeviceId = selectedDevice->id; + deviceUISet->m_selectedDeviceSerial = selectedDevice->serial; + deviceUISet->m_selectedDeviceSequence = selectedDevice->sequence; + deviceUISet->m_selectedDeviceItemImdex = selectedDevice->deviceItemIndex; deviceUISet->m_deviceAPI->getSampleSink()->init(); // Finalize GUI setup and add it to workspace MDI deviceGUI->setDeviceType(DeviceGUI::DeviceTx); deviceGUI->setIndex(deviceSetIndex); - deviceGUI->setWorkspaceIndex(workspace->getIndex()); deviceGUI->setToolTip(samplingDevice->displayedName); deviceGUI->setTitle(samplingDevice->displayedName.split(" ")[0]); deviceGUI->setCurrentDeviceIndex(selectedDeviceIndex); - workspace->addToMdiArea((QMdiSubWindow*) deviceGUI); + MainSpectrumGUI *spectrumGUI = deviceUISet->m_mainSpectrumGUI; + spectrumGUI->setDeviceType(MainSpectrumGUI::DeviceTx); + spectrumGUI->setIndex(deviceSetIndex); + spectrumGUI->setToolTip(samplingDevice->displayedName); + spectrumGUI->setTitle(samplingDevice->displayedName.split(" ")[0]); } void MainWindow::sampleMIMOAdd(Workspace *workspace, int deviceIndex) @@ -723,16 +762,27 @@ void MainWindow::sampleMIMOAdd(Workspace *workspace, int deviceIndex) deviceIndex = DeviceEnumerator::instance()->getTestMIMODeviceIndex(); // create a test MIMO by default } - sampleMIMOImplement(deviceSetIndex, deviceIndex, deviceAPI, m_deviceUIs.back(), workspace); + sampleMIMOCreate(deviceSetIndex, deviceIndex, deviceAPI, m_deviceUIs.back()); + m_deviceUIs.back()->m_deviceGUI->setWorkspaceIndex(workspace->getIndex()); + m_deviceUIs.back()->m_mainSpectrumGUI->setWorkspaceIndex(workspace->getIndex()); + + QObject::connect( + m_deviceUIs.back()->m_mainSpectrumGUI, + &MainSpectrumGUI::moveToWorkspace, + this, + [=](int wsIndexDest){ this->mainSpectrumMove(m_deviceUIs.back()->m_mainSpectrumGUI, wsIndexDest); } + ); + + workspace->addToMdiArea(m_deviceUIs.back()->m_deviceGUI); + workspace->addToMdiArea(m_deviceUIs.back()->m_mainSpectrumGUI); emit m_mainCore->deviceSetAdded(deviceSetIndex, deviceAPI); } -void MainWindow::sampleMIMOImplement( +void MainWindow::sampleMIMOCreate( int deviceSetIndex, int deviceIndex, DeviceAPI *deviceAPI, - DeviceUISet *deviceUISet, - Workspace *workspace + DeviceUISet *deviceUISet ) { int selectedDeviceIndex = deviceIndex; @@ -806,20 +856,28 @@ void MainWindow::sampleMIMOImplement( ); deviceAPI->getSampleMIMO()->setMessageQueueToGUI(deviceGUI->getInputMessageQueue()); deviceUISet->m_deviceGUI = deviceGUI; + const PluginInterface::SamplingDevice *selectedDevice = DeviceEnumerator::instance()->getRxSamplingDevice(selectedDeviceIndex); + deviceUISet->m_selectedDeviceId = selectedDevice->id; + deviceUISet->m_selectedDeviceSerial = selectedDevice->serial; + deviceUISet->m_selectedDeviceSequence = selectedDevice->sequence; + deviceUISet->m_selectedDeviceItemImdex = selectedDevice->deviceItemIndex; deviceUISet->m_deviceAPI->getSampleMIMO()->init(); // Finalize GUI setup and add it to workspace MDI deviceGUI->setDeviceType(DeviceGUI::DeviceMIMO); deviceGUI->setIndex(deviceSetIndex); - deviceGUI->setWorkspaceIndex(workspace->getIndex()); deviceGUI->setToolTip(samplingDevice->displayedName); deviceGUI->setTitle(samplingDevice->displayedName.split(" ")[0]); deviceGUI->setCurrentDeviceIndex(selectedDeviceIndex); - workspace->addToMdiArea((QMdiSubWindow*) deviceGUI); + MainSpectrumGUI *spectrumGUI = deviceUISet->m_mainSpectrumGUI; + spectrumGUI->setDeviceType(MainSpectrumGUI::DeviceMIMO); + spectrumGUI->setIndex(deviceSetIndex); + spectrumGUI->setToolTip(samplingDevice->displayedName); + spectrumGUI->setTitle(samplingDevice->displayedName.split(" ")[0]); } void MainWindow::removeLastDevice() { - int removedTabIndex = m_deviceUIs.size() - 1; + int removedDeviceSetIndex = m_deviceUIs.size() - 1; if (m_deviceUIs.back()->m_deviceSourceEngine) // source tab { @@ -837,12 +895,12 @@ void MainWindow::removeLastDevice() m_deviceUIs.back()->m_deviceGUI->destroy(); m_deviceUIs.back()->m_deviceAPI->resetSamplingDeviceId(); m_deviceUIs.back()->m_deviceAPI->getPluginInterface()->deleteSampleSourcePluginInstanceInput( - m_deviceUIs.back()->m_deviceAPI->getSampleSource()); + m_deviceUIs.back()->m_deviceAPI->getSampleSource()); m_deviceUIs.back()->m_deviceAPI->clearBuddiesLists(); // clear old API buddies lists // ui->tabChannels->removeTab(ui->tabChannels->count() - 1); - m_deviceWidgetTabs.removeLast(); + // m_deviceWidgetTabs.removeLast(); // restoreDeviceTabs(); DeviceAPI *sourceAPI = m_deviceUIs.back()->m_deviceAPI; @@ -869,12 +927,12 @@ void MainWindow::removeLastDevice() m_deviceUIs.back()->m_deviceGUI->destroy(); m_deviceUIs.back()->m_deviceAPI->resetSamplingDeviceId(); m_deviceUIs.back()->m_deviceAPI->getPluginInterface()->deleteSampleSinkPluginInstanceOutput( - m_deviceUIs.back()->m_deviceAPI->getSampleSink()); + m_deviceUIs.back()->m_deviceAPI->getSampleSink()); m_deviceUIs.back()->m_deviceAPI->clearBuddiesLists(); // clear old API buddies lists // ui->tabChannels->removeTab(ui->tabChannels->count() - 1); - m_deviceWidgetTabs.removeLast(); + // m_deviceWidgetTabs.removeLast(); // restoreDeviceTabs(); DeviceAPI *sinkAPI = m_deviceUIs.back()->m_deviceAPI; @@ -902,11 +960,11 @@ void MainWindow::removeLastDevice() m_deviceUIs.back()->m_deviceGUI->destroy(); m_deviceUIs.back()->m_deviceAPI->resetSamplingDeviceId(); m_deviceUIs.back()->m_deviceAPI->getPluginInterface()->deleteSampleMIMOPluginInstanceMIMO( - m_deviceUIs.back()->m_deviceAPI->getSampleMIMO()); + m_deviceUIs.back()->m_deviceAPI->getSampleMIMO()); // ui->tabChannels->removeTab(ui->tabChannels->count() - 1); - m_deviceWidgetTabs.removeLast(); + // m_deviceWidgetTabs.removeLast(); // restoreDeviceTabs(); DeviceAPI *mimoAPI = m_deviceUIs.back()->m_deviceAPI; @@ -920,7 +978,7 @@ void MainWindow::removeLastDevice() m_deviceUIs.pop_back(); m_mainCore->removeLastDeviceSet(); - emit m_mainCore->deviceSetRemoved(removedTabIndex); + emit m_mainCore->deviceSetRemoved(removedDeviceSetIndex); } void MainWindow::addFeatureSet() @@ -990,75 +1048,41 @@ void MainWindow::loadSettings() m_mainCore->setLoggingOptions(); } -void MainWindow::loadPresetSettings(const Preset* preset, int tabIndex) +void MainWindow::loadDeviceSetPresetSettings(const Preset* preset, int deviceSetIndex) { - qDebug("MainWindow::loadPresetSettings: preset [%s | %s]", + qDebug("MainWindow::loadDeviceSetPresetSettings: preset [%s | %s]", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); - if (tabIndex >= 0) + if (deviceSetIndex >= 0) { - DeviceUISet *deviceUI = m_deviceUIs[tabIndex]; - deviceUI->m_spectrumGUI->deserialize(preset->getSpectrumConfig()); - deviceUI->m_deviceAPI->loadSamplingDeviceSettings(preset); - - if (deviceUI->m_deviceSourceEngine) { // source device - deviceUI->loadRxChannelSettings(preset, m_pluginManager->getPluginAPI()); - } else if (deviceUI->m_deviceSinkEngine) { // sink device - deviceUI->loadTxChannelSettings(preset, m_pluginManager->getPluginAPI()); - } else if (deviceUI->m_deviceMIMOEngine) { // MIMO device - deviceUI->loadMIMOChannelSettings(preset, m_pluginManager->getPluginAPI()); - } + DeviceUISet *deviceUISet = m_deviceUIs[deviceSetIndex]; + deviceUISet->loadDeviceSetSettings(preset, m_pluginManager->getPluginAPI(), &m_workspaces, nullptr); } - m_spectrumToggleViewAction->setChecked(preset->getShowSpectrum()); + // m_spectrumToggleViewAction->setChecked(preset->getShowSpectrum()); - // has to be last step - if (!preset->getLayout().isEmpty()) { - restoreState(preset->getLayout()); - } + // // has to be last step + // if (!preset->getLayout().isEmpty()) { + // restoreState(preset->getLayout()); + // } // tabifyDockWidget(ui->presetDock, ui->commandsDock); // override this setting // ui->presetDock->raise(); } -void MainWindow::savePresetSettings(Preset* preset, int tabIndex) +void MainWindow::saveDeviceSetPresetSettings(Preset* preset, int deviceSetIndex) { - qDebug("MainWindow::savePresetSettings: preset [%s | %s]", + qDebug("MainWindow::saveDeviceSetPresetSettings: preset [%s | %s]", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); // Save from currently selected source tab //int currentSourceTabIndex = ui->tabInputsView->currentIndex(); - DeviceUISet *deviceUI = m_deviceUIs[tabIndex]; - - if (deviceUI->m_deviceSourceEngine) // source device - { - preset->setSpectrumConfig(deviceUI->m_spectrumGUI->serialize()); - preset->clearChannels(); - preset->setSourcePreset(); - deviceUI->saveRxChannelSettings(preset); - deviceUI->m_deviceAPI->saveSamplingDeviceSettings(preset); - } - else if (deviceUI->m_deviceSinkEngine) // sink device - { - preset->setSpectrumConfig(deviceUI->m_spectrumGUI->serialize()); - preset->clearChannels(); - preset->setSinkPreset(); - deviceUI->saveTxChannelSettings(preset); - deviceUI->m_deviceAPI->saveSamplingDeviceSettings(preset); - } - else if (deviceUI->m_deviceMIMOEngine) // MIMO device - { - preset->setSpectrumConfig(deviceUI->m_spectrumGUI->serialize()); - preset->clearChannels(); - preset->setMIMOPreset(); - deviceUI->saveMIMOChannelSettings(preset); - deviceUI->m_deviceAPI->saveSamplingDeviceSettings(preset); - } - - preset->setShowSpectrum(m_spectrumToggleViewAction->isChecked()); - preset->setLayout(saveState()); + DeviceUISet *deviceUISet = m_deviceUIs[deviceSetIndex]; + deviceUISet->saveDeviceSetSettings(preset); + // preset->setShowSpectrum(m_spectrumToggleViewAction->isChecked()); + // preset->setLayout(saveState()); } void MainWindow::loadFeatureSetPresetSettings(const FeatureSetPreset* preset, int featureSetIndex, Workspace *workspace) @@ -1071,7 +1095,7 @@ void MainWindow::loadFeatureSetPresetSettings(const FeatureSetPreset* preset, in { FeatureUISet *featureSetUI = m_featureUIs[featureSetIndex]; qDebug("MainWindow::loadFeatureSetPresetSettings: m_apiAdapter: %p", m_apiAdapter); - featureSetUI->loadFeatureSetSettings(preset, m_pluginManager->getPluginAPI(), m_apiAdapter, workspace); + featureSetUI->loadFeatureSetSettings(preset, m_pluginManager->getPluginAPI(), m_apiAdapter, &m_workspaces, workspace); } } @@ -1091,14 +1115,20 @@ void MainWindow::saveFeatureSetPresetSettings(FeatureSetPreset* preset, int feat void MainWindow::loadConfiguration(const Configuration *configuration) { - qDebug("MainWindow::loadConfiguration: configuration [%s | %s] %d workspaces", + qDebug("MainWindow::loadConfiguration: configuration [%s | %s] %d workspace(s) - %d device set(s) - %d feature(s)", qPrintable(configuration->getGroup()), qPrintable(configuration->getDescription()), - configuration->getNumberOfWorkspaces() + configuration->getNumberOfWorkspaces(), + configuration->getDeviceSetPresets().size(), + configuration->getFeatureSetPreset().getFeatureCount() ); // Wipe out everything first + // Device sets + while (m_deviceUIs.size() > 0) { + removeLastDevice(); + } // Features m_featureUIs[0]->freeFeatures(); // Workspaces @@ -1113,8 +1143,66 @@ void MainWindow::loadConfiguration(const Configuration *configuration) for (int i = 0; i < configuration->getNumberOfWorkspaces(); i++) { addWorkspace(); } + + if (m_workspaces.size() <= 0) { // cannot go further if there are no workspaces + return; + } + + // Device sets + const QList& deviceSetPresets = configuration->getDeviceSetPresets(); + + for (const auto& deviceSetPreset : deviceSetPresets) + { + if (deviceSetPreset.isSourcePreset()) + { + int bestDeviceIndex = DeviceEnumerator::instance()->getBestRxSamplingDeviceIndex( + deviceSetPreset.getSelectedDevice().m_deviceId, + deviceSetPreset.getSelectedDevice().m_deviceSerial, + deviceSetPreset.getSelectedDevice().m_deviceSequence, + deviceSetPreset.getSelectedDevice().m_deviceItemIndex + ); + int workspaceIndex = deviceSetPreset.getDeviceWorkspaceIndex() < m_workspaces.size() ? + deviceSetPreset.getDeviceWorkspaceIndex() : + 0; + sampleSourceAdd(m_workspaces[workspaceIndex], bestDeviceIndex); + } + else if (deviceSetPreset.isSinkPreset()) + { + int bestDeviceIndex = DeviceEnumerator::instance()->getBestTxSamplingDeviceIndex( + deviceSetPreset.getSelectedDevice().m_deviceId, + deviceSetPreset.getSelectedDevice().m_deviceSerial, + deviceSetPreset.getSelectedDevice().m_deviceSequence, + deviceSetPreset.getSelectedDevice().m_deviceItemIndex + ); + int workspaceIndex = deviceSetPreset.getDeviceWorkspaceIndex() < m_workspaces.size() ? + deviceSetPreset.getDeviceWorkspaceIndex() : + 0; + sampleSinkAdd(m_workspaces[workspaceIndex], bestDeviceIndex); + } else if (deviceSetPreset.isMIMOPreset()) + { + int bestDeviceIndex = DeviceEnumerator::instance()->getBestMIMOSamplingDeviceIndex( + deviceSetPreset.getSelectedDevice().m_deviceId, + deviceSetPreset.getSelectedDevice().m_deviceSerial, + deviceSetPreset.getSelectedDevice().m_deviceSequence + ); + int workspaceIndex = deviceSetPreset.getDeviceWorkspaceIndex() < m_workspaces.size() ? + deviceSetPreset.getDeviceWorkspaceIndex() : + 0; + sampleMIMOAdd(m_workspaces[workspaceIndex], bestDeviceIndex); + } + + m_deviceUIs.back()->m_deviceGUI->restoreGeometry(deviceSetPreset.getDeviceGeometry()); + m_deviceUIs.back()->m_mainSpectrumGUI->restoreGeometry(deviceSetPreset.getSpectrumGeometry()); + m_deviceUIs.back()->loadDeviceSetSettings(&deviceSetPreset, m_pluginManager->getPluginAPI(), &m_workspaces, nullptr); + } // Features - m_featureUIs[0]->loadFeatureSetSettings(&configuration->getFeatureSetPreset(), m_pluginManager->getPluginAPI(), m_apiAdapter, m_workspaces); + m_featureUIs[0]->loadFeatureSetSettings( + &configuration->getFeatureSetPreset(), + m_pluginManager->getPluginAPI(), + m_apiAdapter, + &m_workspaces, + nullptr + ); for (int i = 0; i < m_featureUIs[0]->getNumberOfFeatures(); i++) { @@ -1142,6 +1230,18 @@ void MainWindow::saveConfiguration(Configuration *configuration) ); configuration->clearData(); + QList& deviceSetPresets = configuration->getDeviceSetPresets(); + + for (const auto& deviceUISet : m_deviceUIs) + { + deviceSetPresets.push_back(Preset()); + deviceUISet->saveDeviceSetSettings(&deviceSetPresets.back()); + deviceSetPresets.back().setSpectrumGeometry(deviceUISet->m_mainSpectrumGUI->saveGeometry()); + deviceSetPresets.back().setSpectrumWorkspaceIndex(deviceUISet->m_mainSpectrumGUI->getWorkspaceIndex()); + deviceSetPresets.back().setDeviceGeometry(deviceUISet->m_deviceGUI->saveGeometry()); + deviceSetPresets.back().setDeviceWorkspaceIndex(deviceUISet->m_deviceGUI->getWorkspaceIndex()); + } + m_featureUIs[0]->saveFeatureSetSettings(&configuration->getFeatureSetPreset()); for (const auto& workspace : m_workspaces) { @@ -1279,15 +1379,14 @@ void MainWindow::closeEvent(QCloseEvent *closeEvent) s.setValue("mainWindowGeometry", qCompress(saveGeometry()).toBase64()); s.setValue("mainWindowState", qCompress(saveState()).toBase64()); - // savePresetSettings(m_mainCore->m_settings.getWorkingPreset(), 0); + // saveDeviceSetPresetSettings(m_mainCore->m_settings.getWorkingPreset(), 0); // saveFeatureSetPresetSettings(m_mainCore->m_settings.getWorkingFeatureSetPreset(), 0); saveConfiguration(m_mainCore->m_settings.getWorkingConfiguration()); m_mainCore->m_settings.save(); - // while (m_deviceUIs.size() > 0) - // { - // removeLastDevice(); - // } + while (m_deviceUIs.size() > 0) { + removeLastDevice(); + } closeEvent->accept(); } @@ -1348,13 +1447,13 @@ QTreeWidgetItem* MainWindow::addPresetToTree(const Preset* preset) void MainWindow::applySettings() { - // loadPresetSettings(m_mainCore->m_settings.getWorkingPreset(), 0); + // loadDeviceSetPresetSettings(m_mainCore->m_settings.getWorkingPreset(), 0); // loadFeatureSetPresetSettings(m_mainCore->m_settings.getWorkingFeatureSetPreset(), 0); loadConfiguration(m_mainCore->m_settings.getWorkingConfiguration()); m_mainCore->m_settings.sortPresets(); - int middleIndex = m_mainCore->m_settings.getPresetCount() / 2; - QTreeWidgetItem *treeItem; + // int middleIndex = m_mainCore->m_settings.getPresetCount() / 2; + // QTreeWidgetItem *treeItem; // ui->presetTree->clear(); // for (int i = 0; i < m_mainCore->m_settings.getPresetCount(); ++i) @@ -1381,13 +1480,13 @@ bool MainWindow::handleMessage(const Message& cmd) if (MainCore::MsgLoadPreset::match(cmd)) { MainCore::MsgLoadPreset& notif = (MainCore::MsgLoadPreset&) cmd; - loadPresetSettings(notif.getPreset(), notif.getDeviceSetIndex()); + loadDeviceSetPresetSettings(notif.getPreset(), notif.getDeviceSetIndex()); return true; } else if (MainCore::MsgSavePreset::match(cmd)) { // MainCore::MsgSavePreset& notif = (MainCore::MsgSavePreset&) cmd; - // savePresetSettings(notif.getPreset(), notif.getDeviceSetIndex()); + // saveDeviceSetPresetSettings(notif.getPreset(), notif.getDeviceSetIndex()); // if (notif.isNewPreset()) { ui->presetTree->setCurrentItem(addPresetToTree(notif.getPreset())); } // m_mainCore->m_settings.sortPresets(); // m_mainCore->m_settings.save(); @@ -1747,7 +1846,7 @@ void MainWindow::on_presetSave_clicked() // if(dlg.exec() == QDialog::Accepted) { // Preset* preset = m_mainCore->m_settings.newPreset(dlg.group(), dlg.description()); - // savePresetSettings(preset, ui->tabInputsView->currentIndex()); + // saveDeviceSetPresetSettings(preset, ui->tabInputsView->currentIndex()); // ui->presetTree->setCurrentItem(addPresetToTree(preset)); // } @@ -1769,7 +1868,7 @@ void MainWindow::on_presetUpdate_clicked() // if (preset != 0) // { // Preset* preset_mod = const_cast(preset); - // savePresetSettings(preset_mod, ui->tabInputsView->currentIndex()); + // saveDeviceSetPresetSettings(preset_mod, ui->tabInputsView->currentIndex()); // changedPreset = preset; // } // } @@ -1943,7 +2042,7 @@ void MainWindow::on_presetImport_clicked() void MainWindow::on_action_saveAll_triggered() { - // savePresetSettings(m_mainCore->m_settings.getWorkingPreset(), ui->tabInputsView->currentIndex()); + // saveDeviceSetPresetSettings(m_mainCore->m_settings.getWorkingPreset(), ui->tabInputsView->currentIndex()); // saveFeatureSetPresetSettings(m_mainCore->m_settings.getWorkingFeatureSetPreset(), ui->tabFeatures->currentIndex()); saveConfiguration(m_mainCore->m_settings.getWorkingConfiguration()); m_mainCore->m_settings.save(); @@ -1971,7 +2070,7 @@ void MainWindow::on_presetLoad_clicked() // return; // } - // loadPresetSettings(preset, ui->tabInputsView->currentIndex()); + // loadDeviceSetPresetSettings(preset, ui->tabInputsView->currentIndex()); } void MainWindow::on_presetDelete_clicked() @@ -2215,7 +2314,9 @@ void MainWindow::sampleSourceChange(int deviceSetIndex, int newDeviceIndex, Work deviceUISet->m_deviceAPI->getPluginInterface()->deleteSampleSourcePluginInstanceInput(deviceUISet->m_deviceAPI->getSampleSource()); deviceUISet->m_deviceAPI->clearBuddiesLists(); // clear old API buddies lists - sampleSourceImplement(deviceSetIndex, newDeviceIndex, deviceUISet->m_deviceAPI, deviceUISet, workspace); + sampleSourceCreate(deviceSetIndex, newDeviceIndex, deviceUISet->m_deviceAPI, deviceUISet); + deviceUISet->m_deviceGUI->setWorkspaceIndex(workspace->getIndex()); + workspace->addToMdiArea(deviceUISet->m_deviceGUI); } } @@ -2235,7 +2336,9 @@ void MainWindow::sampleSinkChange(int deviceSetIndex, int newDeviceIndex, Worksp deviceUISet->m_deviceAPI->getPluginInterface()->deleteSampleSinkPluginInstanceOutput(deviceUISet->m_deviceAPI->getSampleSink()); deviceUISet->m_deviceAPI->clearBuddiesLists(); // clear old API buddies lists - sampleSinkImplement(deviceSetIndex, newDeviceIndex, deviceUISet->m_deviceAPI, deviceUISet, workspace); + sampleSourceCreate(deviceSetIndex, newDeviceIndex, deviceUISet->m_deviceAPI, deviceUISet); + deviceUISet->m_deviceGUI->setWorkspaceIndex(workspace->getIndex()); + workspace->addToMdiArea(deviceUISet->m_deviceGUI); } } @@ -2254,7 +2357,9 @@ void MainWindow::sampleMIMOChange(int deviceSetIndex, int newDeviceIndex, Worksp deviceUISet->m_deviceAPI->resetSamplingDeviceId(); deviceUISet->m_deviceAPI->getPluginInterface()->deleteSampleMIMOPluginInstanceMIMO(deviceUISet->m_deviceAPI->getSampleMIMO()); - sampleMIMOImplement(deviceSetIndex, deviceSetIndex, deviceUISet->m_deviceAPI, deviceUISet, workspace); + sampleSourceCreate(deviceSetIndex, newDeviceIndex, deviceUISet->m_deviceAPI, deviceUISet); + deviceUISet->m_deviceGUI->setWorkspaceIndex(workspace->getIndex()); + workspace->addToMdiArea(deviceUISet->m_deviceGUI); } } @@ -2383,6 +2488,19 @@ void MainWindow::deviceMove(DeviceGUI *gui, int wsIndexDestnation) m_workspaces[wsIndexDestnation]->addToMdiArea(gui); } +void MainWindow::mainSpectrumMove(MainSpectrumGUI *gui, int wsIndexDestnation) +{ + int wsIndexOrigin = gui->getWorkspaceIndex(); + + if (wsIndexOrigin == wsIndexDestnation) { + return; + } + + m_workspaces[wsIndexOrigin]->removeFromMdiArea(gui); + gui->setWorkspaceIndex(wsIndexDestnation); + m_workspaces[wsIndexDestnation]->addToMdiArea(gui); +} + void MainWindow::openFeaturePresetsDialog(QPoint p, Workspace *workspace) { FeaturePresetsDialog dialog; @@ -2390,7 +2508,8 @@ void MainWindow::openFeaturePresetsDialog(QPoint p, Workspace *workspace) dialog.setPresets(m_mainCore->m_settings.getFeatureSetPresets()); dialog.setPluginAPI(m_pluginManager->getPluginAPI()); dialog.setWebAPIAdapter(m_apiAdapter); - dialog.setWorkspace(workspace); + dialog.setCurrentWorkspace(workspace); + dialog.setWorkspaces(&m_workspaces); dialog.populateTree(); dialog.move(p); dialog.exec(); diff --git a/sdrgui/mainwindow.h b/sdrgui/mainwindow.h index 65d2673b8..1a5151365 100644 --- a/sdrgui/mainwindow.h +++ b/sdrgui/mainwindow.h @@ -40,6 +40,7 @@ class DSPDeviceSourceEngine; class DSPDeviceSinkEngine; class Indicator; class GLSpectrumGUI; +class MainSpectrumGUI; class PluginAPI; class ChannelGUI; class ChannelMarker; @@ -123,8 +124,8 @@ private: QProcess *m_fftWisdomProcess; void loadSettings(); - void loadPresetSettings(const Preset* preset, int tabIndex); - void savePresetSettings(Preset* preset, int tabIndex); + void loadDeviceSetPresetSettings(const Preset* preset, int deviceSetIndex); + void saveDeviceSetPresetSettings(Preset* preset, int deviceSetIndex); void loadFeatureSetPresetSettings(const FeatureSetPreset* preset, int featureSetIndex, Workspace *workspace); void saveFeatureSetPresetSettings(FeatureSetPreset* preset, int featureSetIndex); @@ -145,26 +146,23 @@ private: void sampleSourceChange(int deviceSetIndex, int newDeviceIndex, Workspace *workspace); void sampleSinkChange(int deviceSetIndex, int newDeviceIndex, Workspace *workspace); void sampleMIMOChange(int deviceSetIndex, int newDeviceIndex, Workspace *workspace); - void sampleSourceImplement( + void sampleSourceCreate( int deviceSetIndex, int deviceIndex, DeviceAPI *deviceAPI, - DeviceUISet *deviceUISet, - Workspace *workspace + DeviceUISet *deviceUISet ); - void sampleSinkImplement( + void sampleSinkCreate( int deviceSetIndex, int deviceIndex, DeviceAPI *deviceAPI, - DeviceUISet *deviceUISet, - Workspace *workspace + DeviceUISet *deviceUISet ); - void sampleMIMOImplement( + void sampleMIMOCreate( int deviceSetIndex, int deviceIndex, DeviceAPI *deviceAPI, - DeviceUISet *deviceUISet, - Workspace *workspace + DeviceUISet *deviceUISet ); void deleteFeature(int featureSetIndex, int featureIndex); @@ -209,6 +207,7 @@ private slots: void featureMove(FeatureGUI *gui, int wsIndexDestnation); void openFeaturePresetsDialog(QPoint p, Workspace *workspace); void deviceMove(DeviceGUI *gui, int wsIndexDestnation); + void mainSpectrumMove(MainSpectrumGUI *gui, int wsIndexDestnation); void on_action_Quick_Start_triggered(); void on_action_Main_Window_triggered(); void on_action_Loaded_Plugins_triggered(); diff --git a/sdrgui/resources/channels.png b/sdrgui/resources/channels.png new file mode 100644 index 0000000000000000000000000000000000000000..14c223fe418d78989d434b87769bc43355be17d6 GIT binary patch literal 4926 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 JlrN7={V#bgKNtW2 literal 0 HcmV?d00001 diff --git a/sdrgui/resources/res.qrc b/sdrgui/resources/res.qrc index bb851fc62..4b39bd761 100644 --- a/sdrgui/resources/res.qrc +++ b/sdrgui/resources/res.qrc @@ -117,6 +117,8 @@ tx.png mimo.png tool.png + tool_add.png + tool_star.png gear.png corner_topleft.png corner_topright.png @@ -130,6 +132,8 @@ shrink.png exit.png hide.png + channels.png + channels_add.png LiberationMono-Regular.ttf LiberationSans-Regular.ttf diff --git a/sdrgui/resources/tool_add.png b/sdrgui/resources/tool_add.png new file mode 100644 index 0000000000000000000000000000000000000000..479974212c63469605517813abb90f8d1a4d789d GIT binary patch literal 5953 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 GWB(0JOmzML literal 0 HcmV?d00001 diff --git a/sdrgui/resources/tool_star.png b/sdrgui/resources/tool_star.png new file mode 100644 index 0000000000000000000000000000000000000000..530c6a6b11b589f68650f98f87daf15544e0a74e GIT binary patch literal 5865 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 cll9@3Z7D})xM682&?v#I%