diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c135e2..14908ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,7 @@ SET (cubicsdr_sources src/AppFrame.cpp src/AppConfig.cpp src/FrequencyDialog.cpp + src/DemodLabelDialog.cpp src/IOThread.cpp src/ModemProperties.cpp src/sdr/SDRDeviceInfo.cpp @@ -368,6 +369,7 @@ SET (cubicsdr_headers src/AppFrame.h src/AppConfig.h src/FrequencyDialog.h + src/DemodLabelDialog.h src/IOThread.h src/ModemProperties.h src/sdr/SDRDeviceInfo.h diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 902e258..7dab955 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -1561,6 +1561,7 @@ void AppFrame::saveSession(std::string fileName) { *demod->newChild("bandwidth") = (*instance_i)->getBandwidth(); *demod->newChild("frequency") = (*instance_i)->getFrequency(); *demod->newChild("type") = (*instance_i)->getDemodulatorType(); + *demod->newChild("user_label") = (*instance_i)->getDemodulatorUserLabel(); *demod->newChild("squelch_level") = (*instance_i)->getSquelchLevel(); *demod->newChild("squelch_enabled") = (*instance_i)->isSquelchEnabled() ? 1 : 0; *demod->newChild("output_device") = outputDevices[(*instance_i)->getOutputDevice()].name; @@ -1659,6 +1660,7 @@ bool AppFrame::loadSession(std::string fileName) { float gain = demod->hasAnother("gain") ? (float) *demod->getNext("gain") : 1.0; std::string type = "FM"; + DataNode *demodTypeNode = demod->hasAnother("type")?demod->getNext("type"):nullptr; @@ -1688,6 +1690,18 @@ bool AppFrame::loadSession(std::string fileName) { demodTypeNode->element()->get(type); } + //read the user label associated with the demodulator + std::string user_label = ""; + + DataNode *demodUserLabel = demod->hasAnother("user_label") ? demod->getNext("user_label") : nullptr; + + if (demodUserLabel) { + //toString() re-formats strings recognized as numerals, but at least it works for + //all kind of data. + user_label = demodUserLabel->element()->toString(); + } + + ModemSettings mSettings; if (demod->hasAnother("settings")) { @@ -1702,6 +1716,8 @@ bool AppFrame::loadSession(std::string fileName) { } } } + + newDemod = wxGetApp().getDemodMgr().newThread(); @@ -1711,6 +1727,7 @@ bool AppFrame::loadSession(std::string fileName) { numDemodulators++; newDemod->setDemodulatorType(type); + newDemod->setDemodulatorUserLabel(user_label); newDemod->writeModemSettings(mSettings); newDemod->setBandwidth(bandwidth); newDemod->setFrequency(freq); diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp index ba55084..2ee7d21 100644 --- a/src/CubicSDR.cpp +++ b/src/CubicSDR.cpp @@ -731,6 +731,19 @@ void CubicSDR::showFrequencyInput(FrequencyDialog::FrequencyDialogTarget targetM fdialog.ShowModal(); } +void CubicSDR::showLabelInput() { + + DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator(); + + if (activeDemod != nullptr) { + + const wxString demodTitle("Edit Demodulator label"); + + DemodLabelDialog labelDialog(appframe, -1, demodTitle, activeDemod, wxPoint(-100, -100), wxSize(500, 75), wxDEFAULT_DIALOG_STYLE); + labelDialog.ShowModal(); + } +} + AppFrame *CubicSDR::getAppFrame() { return appframe; } diff --git a/src/CubicSDR.h b/src/CubicSDR.h index a6e261f..1402816 100644 --- a/src/CubicSDR.h +++ b/src/CubicSDR.h @@ -22,6 +22,7 @@ #include "AppConfig.h" #include "AppFrame.h" #include "FrequencyDialog.h" +#include "DemodLabelDialog.h" #include "ScopeVisualProcessor.h" #include "SpectrumVisualProcessor.h" @@ -128,6 +129,7 @@ public: int getPPM(); void showFrequencyInput(FrequencyDialog::FrequencyDialogTarget targetMode = FrequencyDialog::FDIALOG_TARGET_DEFAULT, wxString initString = ""); + void showLabelInput(); AppFrame *getAppFrame(); bool areDevicesReady(); diff --git a/src/DemodLabelDialog.cpp b/src/DemodLabelDialog.cpp new file mode 100644 index 0000000..3bb71a0 --- /dev/null +++ b/src/DemodLabelDialog.cpp @@ -0,0 +1,94 @@ +#include "DemodLabelDialog.h" + +#include "wx/clipbrd.h" +#include +#include "CubicSDR.h" + +wxBEGIN_EVENT_TABLE(DemodLabelDialog, wxDialog) +EVT_CHAR_HOOK(DemodLabelDialog::OnChar) +EVT_SHOW(DemodLabelDialog::OnShow) +wxEND_EVENT_TABLE() + +DemodLabelDialog::DemodLabelDialog(wxWindow * parent, wxWindowID id, const wxString & title, + DemodulatorInstance *demod, const wxPoint & position, + const wxSize & size, long style) : + wxDialog(parent, id, title, position, size, style) { + + wxString labelStr; + + //by construction, is allways != nullptr + activeDemod = demod; + + labelStr = activeDemod->getDemodulatorUserLabel(); + + if (labelStr.empty()) { + //propose a default value... + labelStr = activeDemod->getDemodulatorType(); + } + + + dialogText = new wxTextCtrl(this, wxID_LABEL_INPUT, labelStr, wxPoint(6, 1), wxSize(size.GetWidth() - 20, size.GetHeight() - 70), + wxTE_PROCESS_ENTER); + dialogText->SetFont(wxFont(15, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD)); + + Centre(); + + dialogText->SetValue(labelStr); + dialogText->SetSelection(-1, -1); +} + + +void DemodLabelDialog::OnChar(wxKeyEvent& event) { + int c = event.GetKeyCode(); + + std::string strValue = dialogText->GetValue().ToStdString(); + + switch (c) { + case WXK_RETURN: + case WXK_NUMPAD_ENTER: + + //No need to display the demodulator type twice if the user do not change the default value... + if (strValue != activeDemod->getDemodulatorType()) { + activeDemod->setDemodulatorUserLabel(strValue); + } + else { + activeDemod->setDemodulatorUserLabel(""); + } + + Close(); + break; + case WXK_ESCAPE: + Close(); + break; + } + + if (event.ControlDown() && c == 'V') { + // Alter clipboard contents to remove unwanted chars + wxTheClipboard->Open(); + wxTextDataObject data; + wxTheClipboard->GetData(data); + std::string clipText = data.GetText().ToStdString(); + wxTheClipboard->SetData(new wxTextDataObject(clipText)); + wxTheClipboard->Close(); + event.Skip(); + } + else if (c == WXK_RIGHT || c == WXK_LEFT || event.ControlDown()) { + event.Skip(); + + } + else { +#ifdef __linux__ + dialogText->OnChar(event); + event.Skip(); +#else + event.DoAllowNextEvent(); +#endif + } +} + +void DemodLabelDialog::OnShow(wxShowEvent &event) { + + dialogText->SetFocus(); + dialogText->SetSelection(-1, -1); + event.Skip(); +} diff --git a/src/DemodLabelDialog.h b/src/DemodLabelDialog.h new file mode 100644 index 0000000..a10d34b --- /dev/null +++ b/src/DemodLabelDialog.h @@ -0,0 +1,29 @@ +#pragma once + +#include "wx/dialog.h" +#include "wx/textctrl.h" +#include "wx/string.h" +#include "wx/button.h" +#include "DemodulatorInstance.h" + +#define wxID_LABEL_INPUT 3002 + +class DemodLabelDialog : public wxDialog +{ +public: + + DemodLabelDialog( wxWindow * parent, wxWindowID id, const wxString & title, + DemodulatorInstance *demod = NULL, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = wxDEFAULT_DIALOG_STYLE); + + wxTextCtrl * dialogText; + +private: + DemodulatorInstance *activeDemod = nullptr; + void OnEnter ( wxCommandEvent &event ); + void OnChar ( wxKeyEvent &event ); + void OnShow(wxShowEvent &event); + DECLARE_EVENT_TABLE() +}; diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index e1e6860..f2f7382 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -30,8 +30,7 @@ void DemodVisualCue::step() { } } -DemodulatorInstance::DemodulatorInstance() : - t_PreDemod(nullptr), t_Demod(nullptr), t_Audio(nullptr) { +DemodulatorInstance::DemodulatorInstance() { #if ENABLE_DIGITAL_LAB activeOutput = nullptr; @@ -50,7 +49,9 @@ DemodulatorInstance::DemodulatorInstance() : follow.store(false); tracking.store(false); - label = new std::string("Unnamed"); + label.store(new std::string("Unnamed")); + user_label.store(new std::string()); + pipeIQInputData = new DemodulatorThreadInputQueue; pipeIQDemodData = new DemodulatorThreadPostInputQueue; pipeDemodNotify = new DemodulatorThreadCommandQueue; @@ -149,12 +150,8 @@ std::string DemodulatorInstance::getLabel() { } void DemodulatorInstance::setLabel(std::string labelStr) { - std::string *newLabel = new std::string; - newLabel->append(labelStr); - std::string *oldLabel; - oldLabel = label; - label = newLabel; - delete oldLabel; + + delete label.exchange(new std::string(labelStr)); } bool DemodulatorInstance::isTerminated() { @@ -330,6 +327,15 @@ std::string DemodulatorInstance::getDemodulatorType() { return demodulatorPreThread->getDemodType(); } +std::string DemodulatorInstance::getDemodulatorUserLabel() { + return *(user_label.load()); +} + +void DemodulatorInstance::setDemodulatorUserLabel(const std::string& demod_user_label) { + + delete user_label.exchange(new std::string(demod_user_label)); +} + void DemodulatorInstance::setDemodulatorLock(bool demod_lock_in) { Modem *cModem = demodulatorPreThread->getModem(); if (cModem && cModem->getType() == "digital") { diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h index b5532ae..076c6de 100644 --- a/src/demod/DemodulatorInstance.h +++ b/src/demod/DemodulatorInstance.h @@ -35,12 +35,12 @@ public: pthread_t t_PreDemod; pthread_t t_Demod; #else - std::thread *t_PreDemod; - std::thread *t_Demod; + std::thread *t_PreDemod = nullptr; + std::thread *t_Demod = nullptr; #endif - AudioThread *audioThread; - std::thread *t_Audio; + AudioThread *audioThread = nullptr; + std::thread *t_Audio = nullptr; DemodulatorInstance(); ~DemodulatorInstance(); @@ -73,7 +73,10 @@ public: void setDemodulatorType(std::string demod_type_in); std::string getDemodulatorType(); - + + std::string getDemodulatorUserLabel(); + void setDemodulatorUserLabel(const std::string& demod_user_label); + void setDemodulatorLock(bool demod_lock_in); int getDemodulatorLock(); @@ -136,6 +139,7 @@ protected: private: std::atomic label; // + std::atomic user_label; // std::atomic_bool terminated; // std::atomic_bool demodTerminated; // std::atomic_bool audioTerminated; // diff --git a/src/util/GLFont.h b/src/util/GLFont.h index 041fc49..dc86cc7 100644 --- a/src/util/GLFont.h +++ b/src/util/GLFont.h @@ -91,7 +91,7 @@ public: void doCacheGC(); private: std::map stringCache; - + std::string nextParam(std::istringstream &str); std::string getParamKey(std::string param_str); std::string getParamValue(std::string param_str); diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index 75abef3..0701fd1 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -351,10 +351,9 @@ void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, RGBA4f color, long glEnable(GL_BLEND); - std::string demodStr = ""; GLFont::Align demodAlign = GLFont::GLFONT_ALIGN_CENTER; - demodStr = demod->getDemodulatorType(); + std::string demodStr = demod->getDemodulatorType(); demodAlign = GLFont::GLFONT_ALIGN_CENTER; @@ -373,22 +372,42 @@ void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, RGBA4f color, long // add lock to string if we have an lock if(demod->getDemodulatorLock()) { - demodStr = demodStr + " Lock"; - } + demodStr += " Lock"; + } + // else { // demodStr = demodStr + " UnLock"; // } - glColor3f(0, 0, 0); - GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodStr, 2.0 * (uxPos - 0.5) + xOfs, -1.0 + hPos - yOfs, 16, demodAlign, - GLFont::GLFONT_ALIGN_CENTER, 0, 0, true); - glColor3f(1, 1, 1); - GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodStr, 2.0 * (uxPos - 0.5), -1.0 + hPos, 16, demodAlign, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true); + //demodulator user label if present: type is displayed above the label, which is at the bottom of the screen. + if (!demod->getDemodulatorUserLabel().empty()) { + hPos += 1.3 * labelHeight; + } + + drawSingleDemodLabel(demodStr, uxPos, hPos, xOfs, yOfs, GLFont::GLFONT_ALIGN_CENTER); + + //revert... + if (!demod->getDemodulatorUserLabel().empty()) { + hPos -= 1.3 * labelHeight; + drawSingleDemodLabel(demod->getDemodulatorUserLabel(), uxPos, hPos, xOfs, yOfs, GLFont::GLFONT_ALIGN_CENTER); + } glDisable(GL_BLEND); } +void PrimaryGLContext::drawSingleDemodLabel(std::string demodStr, float uxPos, float hPos, float xOfs, float yOfs, GLFont::Align demodAlign) { + + glColor3f(0, 0, 0); + GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodStr, 2.0 * (uxPos - 0.5) + xOfs, + -1.0 + hPos - yOfs, 16, demodAlign, + GLFont::GLFONT_ALIGN_CENTER, 0, 0, true); + glColor3f(1, 1, 1); + GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodStr, 2.0 * (uxPos - 0.5), + -1.0 + hPos, 16, demodAlign, + GLFont::GLFONT_ALIGN_CENTER, 0, 0, true); +} + void PrimaryGLContext::DrawFreqSelector(float uxPos, RGBA4f color, float w, long long /* center_freq */, long long srate) { DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); diff --git a/src/visual/PrimaryGLContext.h b/src/visual/PrimaryGLContext.h index 5cbda79..303f2c4 100644 --- a/src/visual/PrimaryGLContext.h +++ b/src/visual/PrimaryGLContext.h @@ -24,6 +24,7 @@ public: void DrawFreqSelector(float uxPos, RGBA4f color, float w = 0, long long center_freq = -1, long long srate = 0); void DrawRangeSelector(float uxPos1, float uxPos2, RGBA4f color); void DrawDemod(DemodulatorInstance *demod, RGBA4f color, long long center_freq = -1, long long srate = 0); + void DrawDemodInfo(DemodulatorInstance *demod, RGBA4f color, long long center_freq = -1, long long srate = 0, bool centerline = false); void DrawFreqBwInfo(long long freq, int bw, RGBA4f color, long long center_freq = - 1, long long srate = 0, bool stack = false, bool centerline = false); @@ -31,4 +32,5 @@ public: private: float hoverAlpha; + void drawSingleDemodLabel(std::string demodStr, float uxPos, float hPos, float xOfs, float yOfs, GLFont::Align demodAlign); }; diff --git a/src/visual/TuningCanvas.cpp b/src/visual/TuningCanvas.cpp index 1f20f11..6d58520 100644 --- a/src/visual/TuningCanvas.cpp +++ b/src/visual/TuningCanvas.cpp @@ -430,7 +430,7 @@ void TuningCanvas::OnKeyDown(wxKeyEvent& event) { } else if (hoverState == TUNING_HOVER_BW) { wxGetApp().showFrequencyInput(FrequencyDialog::FDIALOG_TARGET_BANDWIDTH); } - } + } } void TuningCanvas::OnKeyUp(wxKeyEvent& event) { diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index d65c98b..8b4bbfe 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -451,6 +451,9 @@ void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { case WXK_SPACE: wxGetApp().showFrequencyInput(); break; + case 'E': //E is for 'Edit the label' of the active demodulator. + wxGetApp().showLabelInput(); + break; case 'C': if (wxGetApp().getDemodMgr().getActiveDemodulator()) { wxGetApp().setFrequency(wxGetApp().getDemodMgr().getActiveDemodulator()->getFrequency()); @@ -568,14 +571,14 @@ void WaterfallCanvas::updateHoverState() { mouseTracker.setVertDragLock(true); mouseTracker.setHorizDragLock(false); - setStatusText("Click and drag to change demodulator bandwidth. SPACE or numeric key for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center."); + setStatusText("Click and drag to change demodulator bandwidth. SPACE or numeric key for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label."); } else { SetCursor(wxCURSOR_SIZING); nextDragState = WF_DRAG_FREQUENCY; mouseTracker.setVertDragLock(true); mouseTracker.setHorizDragLock(false); - setStatusText("Click and drag to change demodulator frequency; SPACE or numeric key for direct input. [, ] to nudge, M for mute, D to delete, C to center."); + setStatusText("Click and drag to change demodulator frequency; SPACE or numeric key for direct input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label."); } } else { SetCursor(wxCURSOR_CROSS);