diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a65c6b..d82e51a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") SET(CUBICSDR_VERSION_MAJOR "0") SET(CUBICSDR_VERSION_MINOR "2") -SET(CUBICSDR_VERSION_PATCH "2") +SET(CUBICSDR_VERSION_PATCH "3") SET(CUBICSDR_VERSION_SUFFIX "") SET(CUBICSDR_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}${CUBICSDR_VERSION_SUFFIX}") @@ -347,6 +347,10 @@ SET (cubicsdr_sources src/modules/modem/analog/ModemLSB.cpp src/modules/modem/analog/ModemUSB.cpp src/audio/AudioThread.cpp + src/audio/AudioSinkThread.cpp + src/audio/AudioSinkFileThread.cpp + src/audio/AudioFile.cpp + src/audio/AudioFileWAV.cpp src/util/Gradient.cpp src/util/Timer.cpp src/util/MouseTracker.cpp @@ -451,6 +455,10 @@ SET (cubicsdr_headers src/modules/modem/analog/ModemLSB.h src/modules/modem/analog/ModemUSB.h src/audio/AudioThread.h + src/audio/AudioSinkThread.h + src/audio/AudioSinkFileThread.h + src/audio/AudioFile.h + src/audio/AudioFileWAV.h src/util/Gradient.h src/util/Timer.h src/util/ThreadBlockingQueue.h @@ -995,7 +1003,7 @@ IF (WIN32 AND BUILD_INSTALLER) IF (MSVC) install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/external/msvc/${EX_PLATFORM_NAME}/vc_redist.${EX_PLATFORM_NAME}.exe DESTINATION vc_redist) - set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\vc_redist\\\\vc_redist.${EX_PLATFORM_NAME}.exe\\\" /q:a'") + set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\vc_redist\\\\vc_redist.${EX_PLATFORM_NAME}.exe\\\" /passive /norestart'") ENDIF (MSVC) diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp index 2315883..f85ab7c 100644 --- a/src/AppConfig.cpp +++ b/src/AppConfig.cpp @@ -4,6 +4,8 @@ #include "AppConfig.h" #include "CubicSDR.h" +#include + DeviceConfig::DeviceConfig() : deviceId("") { ppm.store(0); offset.store(0); @@ -505,6 +507,51 @@ bool AppConfig::getBookmarksVisible() { return bookmarksVisible.load(); } +void AppConfig::setRecordingPath(std::string recPath) { + recordingPath = recPath; +} + +std::string AppConfig::getRecordingPath() { + return recordingPath; +} + +bool AppConfig::verifyRecordingPath() { + string recPathStr = wxGetApp().getConfig()->getRecordingPath(); + + if (recPathStr.empty()) { + wxMessageBox( wxT("Recording path is not set. Please use 'Set Recording Path' from the 'File' Menu."), wxT("Recording Path Error"), wxICON_INFORMATION); + + return false; + } + + wxFileName recPath(recPathStr); + + if (!recPath.Exists() || !recPath.IsDirWritable()) { + wxMessageBox( wxT("Recording path does not exist or is not writable. Please use 'Set Recording Path' from the 'File' Menu."), wxT("Recording Path Error"), wxICON_INFORMATION); + + return false; + } + + return true; +} + + +void AppConfig::setRecordingSquelchOption(int enumChoice) { + recordingSquelchOption = enumChoice; +} + +int AppConfig::getRecordingSquelchOption() { + return recordingSquelchOption; +} + +void AppConfig::setRecordingFileTimeLimit(int nbSeconds) { + recordingFileTimeLimitSeconds = nbSeconds; +} + +int AppConfig::getRecordingFileTimeLimit() { + return recordingFileTimeLimitSeconds; +} + void AppConfig::setConfigName(std::string configName) { this->configName = configName; @@ -559,6 +606,12 @@ bool AppConfig::save() { *window_node->newChild("bookmark_visible") = bookmarksVisible.load(); } + //Recording settings: + DataNode *rec_node = cfg.rootNode()->newChild("recording"); + *rec_node->newChild("path") = recordingPath; + *rec_node->newChild("squelch") = recordingSquelchOption; + *rec_node->newChild("file_time_limit") = recordingFileTimeLimitSeconds; + DataNode *devices_node = cfg.rootNode()->newChild("devices"); std::map::iterator device_config_i; @@ -741,6 +794,26 @@ bool AppConfig::load() { } } + //Recording settings: + if (cfg.rootNode()->hasAnother("recording")) { + DataNode *rec_node = cfg.rootNode()->getNext("recording"); + + if (rec_node->hasAnother("path")) { + DataNode *rec_path = rec_node->getNext("path"); + recordingPath = rec_path->element()->toString(); + } + + if (rec_node->hasAnother("squelch")) { + DataNode *rec_squelch = rec_node->getNext("squelch"); + rec_squelch->element()->get(recordingSquelchOption); + } + + if (rec_node->hasAnother("file_time_limit")) { + DataNode *rec_file_time_limit = rec_node->getNext("file_time_limit"); + rec_file_time_limit->element()->get(recordingFileTimeLimitSeconds); + } + } + if (cfg.rootNode()->hasAnother("devices")) { DataNode *devices_node = cfg.rootNode()->getNext("devices"); diff --git a/src/AppConfig.h b/src/AppConfig.h index 75614b2..c1fe392 100644 --- a/src/AppConfig.h +++ b/src/AppConfig.h @@ -138,6 +138,16 @@ public: void setBookmarksVisible(bool state); bool getBookmarksVisible(); + //Recording settings: + void setRecordingPath(std::string recPath); + std::string getRecordingPath(); + bool verifyRecordingPath(); + + void setRecordingSquelchOption(int enumChoice); + int getRecordingSquelchOption(); + + void setRecordingFileTimeLimit(int nbSeconds); + int getRecordingFileTimeLimit(); #if USE_HAMLIB int getRigModel(); @@ -185,6 +195,10 @@ private: std::atomic_int dbOffset; std::vector manualDevices; std::atomic_bool bookmarksVisible; + + std::string recordingPath = ""; + int recordingSquelchOption = 0; + int recordingFileTimeLimitSeconds = 0; #if USE_HAMLIB std::atomic_int rigModel, rigRate; std::string rigPort; diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 078482c..d17a02e 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -18,12 +18,13 @@ #include #include -#include "AudioThread.h" +#include "AudioSinkFileThread.h" #include "CubicSDR.h" #include "DataTree.h" #include "ColorTheme.h" #include "DemodulatorMgr.h" #include "ImagePanel.h" +#include "ActionDialog.h" #include #include @@ -53,6 +54,22 @@ wxEND_EVENT_TABLE() #endif + +class ActionDialogBookmarkReset : public ActionDialog { +public: + ActionDialogBookmarkReset() : ActionDialog(wxGetApp().getAppFrame(), wxID_ANY, wxT("Reset Bookmarks?")) { + m_questionText->SetLabelText(wxT("Resetting bookmarks will erase all current bookmarks; are you sure?")); + } + + void doClickOK() { + wxGetApp().getBookmarkMgr().resetBookmarks(); + wxGetApp().getBookmarkMgr().updateBookmarks(); + wxGetApp().getBookmarkMgr().updateActiveList(); + } +}; + + + /* split a string by 'seperator' into a vector of string */ std::vector str_explode(const std::string &seperator, const std::string &in_str); @@ -402,49 +419,13 @@ AppFrame::AppFrame() : // Make a menubar menuBar = new wxMenuBar; - wxMenu *menu = new wxMenu; -#ifndef __APPLE__ -#ifdef CUBICSDR_ENABLE_ABOUT_DIALOG - menu->Append(wxID_ABOUT_CUBICSDR, "About " CUBICSDR_INSTALL_NAME); -#endif -#endif - menu->Append(wxID_SDR_DEVICES, "SDR Devices"); - menu->AppendSeparator(); - menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); - menu->AppendSeparator(); - menu->Append(wxID_OPEN, "&Open Session"); - menu->Append(wxID_SAVE, "&Save Session"); - menu->Append(wxID_SAVEAS, "Save Session &As.."); - menu->AppendSeparator(); - menu->Append(wxID_RESET, "&Reset Session"); - menu->AppendSeparator(); - menu->Append(wxID_OPEN_BOOKMARKS, "Open Bookmarks"); - menu->Append(wxID_SAVE_BOOKMARKS, "Save Bookmarks"); - menu->Append(wxID_SAVEAS_BOOKMARKS, "Save Bookmarks As.."); - menu->AppendSeparator(); - menu->Append(wxID_RESET_BOOKMARKS, "Reset Bookmarks"); - -#ifndef __APPLE__ - menu->AppendSeparator(); - menu->Append(wxID_CLOSE); -#else -#ifdef CUBICSDR_ENABLE_ABOUT_DIALOG - if ( wxApp::s_macAboutMenuItemId != wxID_NONE ) { - wxString aboutLabel; - aboutLabel.Printf(_("About %s"), CUBICSDR_INSTALL_NAME); - menu->Append( wxApp::s_macAboutMenuItemId, aboutLabel); - } -#endif -#endif - - menuBar->Append(menu, wxT("&File")); + + menuBar->Append(makeFileMenu(), wxT("&File")); settingsMenu = new wxMenu; menuBar->Append(settingsMenu, wxT("&Settings")); - menu = new wxMenu; - std::vector::iterator devices_i; std::map::iterator mdevices_i; AudioThread::enumerateDevices(devices); @@ -478,7 +459,7 @@ AppFrame::AppFrame() : menuBar->Append(sampleRateMenu, wxT("Sample &Rate")); // Audio Sample Rates - menu = new wxMenu; + wxMenu *audioSampleRateMenu = new wxMenu; #define NUM_RATES_DEFAULT 4 unsigned int desired_rates[NUM_RATES_DEFAULT] = { 48000, 44100, 96000, 192000 }; @@ -508,7 +489,7 @@ AppFrame::AppFrame() : for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) { int menu_id = wxID_AUDIO_BANDWIDTH_BASE + wxID_AUDIO_DEVICE_MULTIPLIER * mdevices_i->first; wxMenu *subMenu = new wxMenu; - menu->AppendSubMenu(subMenu, mdevices_i->second.name, wxT("Description?")); + audioSampleRateMenu->AppendSubMenu(subMenu, mdevices_i->second.name, wxT("Description?")); int j = 0; for (std::vector::iterator srate = mdevices_i->second.sampleRates.begin(); srate != mdevices_i->second.sampleRates.end(); @@ -526,7 +507,12 @@ AppFrame::AppFrame() : } } - menuBar->Append(menu, wxT("Audio &Sample Rate")); + menuBar->Append(audioSampleRateMenu, wxT("Audio &Sample Rate")); + + //Add a Recording menu + menuBar->Append(makeRecordingMenu(), wxT("Recordin&g")); + // + updateRecordingMenu(); //Add Display menu displayMenu = new wxMenu; @@ -535,7 +521,7 @@ AppFrame::AppFrame() : int fontScale = wxGetApp().getConfig()->getFontScale(); - fontMenu->AppendRadioItem(wxID_DISPLAY_BASE, "Normal")->Check(GLFont::GLFONT_SCALE_NORMAL == fontScale); + fontMenu->AppendRadioItem(wxID_DISPLAY_BASE, "Default")->Check(GLFont::GLFONT_SCALE_NORMAL == fontScale); fontMenu->AppendRadioItem(wxID_DISPLAY_BASE + 1, "1.5x")->Check(GLFont::GLFONT_SCALE_MEDIUM == fontScale); fontMenu->AppendRadioItem(wxID_DISPLAY_BASE + 2, "2.0x")->Check(GLFont::GLFONT_SCALE_LARGE == fontScale); @@ -729,6 +715,137 @@ AppFrame::~AppFrame() { t_FFTData->join(); } +wxMenu *AppFrame::makeFileMenu() { + + wxMenu *menu = new wxMenu; +#ifndef __APPLE__ +#ifdef CUBICSDR_ENABLE_ABOUT_DIALOG + menu->Append(wxID_ABOUT_CUBICSDR, "About " CUBICSDR_INSTALL_NAME); +#endif +#endif + menu->Append(wxID_SDR_DEVICES, "SDR Devices"); + menu->AppendSeparator(); + menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); + menu->AppendSeparator(); + + wxMenu *sessionMenu = new wxMenu; + + sessionMenu->Append(wxID_OPEN, "&Open Session"); + sessionMenu->Append(wxID_SAVE, "&Save Session"); + sessionMenu->Append(wxID_SAVEAS, "Save Session &As.."); + sessionMenu->AppendSeparator(); + sessionMenu->Append(wxID_RESET, "&Reset Session"); + + menu->AppendSubMenu(sessionMenu, "Session"); + + menu->AppendSeparator(); + + wxMenu *bookmarkMenu = new wxMenu; + + bookmarkMenu->Append(wxID_OPEN_BOOKMARKS, "Open Bookmarks"); + bookmarkMenu->Append(wxID_SAVE_BOOKMARKS, "Save Bookmarks"); + bookmarkMenu->Append(wxID_SAVEAS_BOOKMARKS, "Save Bookmarks As.."); + bookmarkMenu->AppendSeparator(); + bookmarkMenu->Append(wxID_RESET_BOOKMARKS, "Reset Bookmarks"); + + menu->AppendSubMenu(bookmarkMenu, "Bookmarks"); + +#ifndef __APPLE__ + menu->AppendSeparator(); + menu->Append(wxID_CLOSE); +#else +#ifdef CUBICSDR_ENABLE_ABOUT_DIALOG + if (wxApp::s_macAboutMenuItemId != wxID_NONE) { + wxString aboutLabel; + aboutLabel.Printf(_("About %s"), CUBICSDR_INSTALL_NAME); + menu->Append(wxApp::s_macAboutMenuItemId, aboutLabel); + } +#endif +#endif + + fileMenu = menu; + + return menu; +} + +wxMenu *AppFrame::makeRecordingMenu() { + + recordingMenuItems.clear(); + + wxMenu *menu = new wxMenu; + + recordingMenuItems[wxID_RECORDING_PATH] = menu->Append(wxID_RECORDING_PATH, getSettingsLabel("Set Recording Path", "")); + + menu->AppendSeparator(); + + //Squelch options as sub-menu: + wxMenu *subMenu = new wxMenu; + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE] = menu->AppendSubMenu(subMenu, "Squelch"); + + recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_SILENCE, "Record Silence", + "Record below squelch-break audio as silence, i.e records as the user may hear."); + recordingMenuItems[wxID_RECORDING_SQUELCH_SKIP] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_SKIP, "Skip Silence", + "Do not record below squelch-break audio, i.e squelch-break audio parts are packed together."); + recordingMenuItems[wxID_RECORDING_SQUELCH_ALWAYS] = subMenu->AppendRadioItem(wxID_RECORDING_SQUELCH_ALWAYS, "Record Always", + "Record everything irrespective of the squelch level."); + + recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT] = menu->Append(wxID_RECORDING_FILE_TIME_LIMIT, getSettingsLabel("File time limit", ""), + "Creates a new file automatically, each time the recording lasts longer than the limit, named according to the current time."); + + recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true); + + recordingMenu = menu; + + return menu; +} + +void AppFrame::updateRecordingMenu() { + + // Recording path: + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + if (recPath.length() > 32) { + recPath = "..." + recPath.substr(recPath.length() - 32, 32); + } + + recordingMenuItems[wxID_RECORDING_PATH]->SetItemLabel(getSettingsLabel("Set Recording Path", recPath.empty() ? "" : recPath)); + + //Squelch options: + int squelchEnumValue = wxGetApp().getConfig()->getRecordingSquelchOption(); + + if (squelchEnumValue == AudioSinkFileThread::SQUELCH_RECORD_SILENCE) { + + recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true); + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Silence")); + + } else if (squelchEnumValue == AudioSinkFileThread::SQUELCH_SKIP_SILENCE) { + + recordingMenuItems[wxID_RECORDING_SQUELCH_SKIP]->Check(true); + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Skip Silence")); + + } else if (squelchEnumValue == AudioSinkFileThread::SQUELCH_RECORD_ALWAYS) { + + recordingMenuItems[wxID_RECORDING_SQUELCH_ALWAYS]->Check(true); + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Always")); + } + else { + recordingMenuItems[wxID_RECORDING_SQUELCH_SILENCE]->Check(true); + recordingMenuItems[wxID_RECORDING_SQUELCH_BASE]->SetItemLabel(getSettingsLabel("Squelch", "Record Silence")); + + } + + //File time limit: + int fileTimeLimitSeconds = wxGetApp().getConfig()->getRecordingFileTimeLimit(); + + if (fileTimeLimitSeconds <= 0) { + + recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT]->SetItemLabel(getSettingsLabel("File time limit","")); + } + else { + recordingMenuItems[wxID_RECORDING_FILE_TIME_LIMIT]->SetItemLabel(getSettingsLabel("File time limit", + std::to_string(fileTimeLimitSeconds), "s")); + } +} + void AppFrame::initDeviceParams(SDRDeviceInfo *devInfo) { this->devInfo = devInfo; deviceChanged.store(true); @@ -1480,16 +1597,81 @@ bool AppFrame::actionOnMenuLoadSave(wxCommandEvent& event) { } else if (event.GetId() == wxID_RESET_BOOKMARKS) { - wxGetApp().getBookmarkMgr().resetBookmarks(); - wxGetApp().getBookmarkMgr().updateBookmarks(); - wxGetApp().getBookmarkMgr().updateActiveList(); - + ActionDialog::showDialog(new ActionDialogBookmarkReset()); + return true; } return false; } +bool AppFrame::actionOnMenuRecording(wxCommandEvent& event) { + + if (event.GetId() == wxID_RECORDING_PATH) { + + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + + wxDirDialog recPathDialog(this, _("File Path for Recordings"), recPath, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if (recPathDialog.ShowModal() == wxID_CANCEL) { + return true; + } + + wxGetApp().getConfig()->setRecordingPath(recPathDialog.GetPath().ToStdString()); + + updateRecordingMenu(); + return true; + + } + else if (event.GetId() == wxID_RECORDING_SQUELCH_SILENCE) { + + wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_RECORD_SILENCE); + + updateRecordingMenu(); + return true; + } + else if (event.GetId() == wxID_RECORDING_SQUELCH_SKIP) { + + wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_SKIP_SILENCE); + + updateRecordingMenu(); + return true; + } + else if (event.GetId() == wxID_RECORDING_SQUELCH_ALWAYS) { + + wxGetApp().getConfig()->setRecordingSquelchOption(AudioSinkFileThread::SQUELCH_RECORD_ALWAYS); + + updateRecordingMenu(); + return true; + } + else if (event.GetId() == wxID_RECORDING_FILE_TIME_LIMIT) { + + int currentFileLimitSeconds = wxGetApp().getConfig()->getRecordingFileTimeLimit(); + + long newFileLimit = wxGetNumberFromUser(wxString("\nFile time limit:\n") + + "\nCreates a new file automatically, each time the recording lasts longer than the limit, named according to the current time.\n\n " + + + "min: 0 s (no limit)" + + ", max: 36000 s (10 hours)\n", + "Time in seconds", + "File Time Limit", + //If a manual sample rate has already been input, recall this one. + currentFileLimitSeconds > 0 ? currentFileLimitSeconds : 0, + 0, + 36000, + this); + + if (newFileLimit != -1) { + + wxGetApp().getConfig()->setRecordingFileTimeLimit((int)newFileLimit); + + updateRecordingMenu(); + } + + return true; + } + + return false; +} + bool AppFrame::actionOnMenuRig(wxCommandEvent& event) { bool bManaged = false; @@ -1709,9 +1891,12 @@ void AppFrame::OnMenu(wxCommandEvent& event) { else if (actionOnMenuAudioSampleRate(event)) { return; } - else if (actionOnMenuDisplay(event)) { + else if (actionOnMenuRecording(event)) { return; } + else if (actionOnMenuDisplay(event)) { + return; + } //Optional : Rig else if (actionOnMenuRig(event)) { return; @@ -2576,6 +2761,7 @@ int AppFrame::OnGlobalKeyDown(wxKeyEvent &event) { case 'S': case 'P': case 'M': + case 'R': return 1; case '0': case '1': @@ -2716,6 +2902,13 @@ int AppFrame::OnGlobalKeyUp(wxKeyEvent &event) { wxGetApp().setSoloMode(!wxGetApp().getSoloMode()); return 1; break; + case 'R': + if (event.ShiftDown()) { + toggleAllActiveDemodRecording(); + } else { + toggleActiveDemodRecording(); + } + break; case 'P': wxGetApp().getSpectrumProcessor()->setPeakHold(!wxGetApp().getSpectrumProcessor()->getPeakHold()); if (wxGetApp().getDemodSpectrumProcessor()) { @@ -2760,6 +2953,43 @@ int AppFrame::OnGlobalKeyUp(wxKeyEvent &event) { return 1; } +void AppFrame::toggleActiveDemodRecording() { + if (!wxGetApp().getConfig()->verifyRecordingPath()) { + return; + } + + DemodulatorInstancePtr activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator(); + + if (activeDemod) { + activeDemod->setRecording(!activeDemod->isRecording()); + wxGetApp().getBookmarkMgr().updateActiveList(); + } +} + +void AppFrame::toggleAllActiveDemodRecording() { + if (!wxGetApp().getConfig()->verifyRecordingPath()) { + return; + } + + auto activeDemods = wxGetApp().getDemodMgr().getDemodulators(); + + bool stateToSet = true; + + for (auto i : activeDemods) { + if (i->isActive() && i->isRecording()) { + stateToSet = false; + break; + } + } + + for (auto i : activeDemods) { + if (i->isActive() && i->isRecording() != stateToSet) { + i->setRecording(stateToSet); + } + } +} + + void AppFrame::setWaterfallLinesPerSecond(int lps) { waterfallSpeedMeter->setUserInputValue(sqrt(lps)); diff --git a/src/AppFrame.h b/src/AppFrame.h index 5ea0fe5..03344ae 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -76,6 +76,13 @@ #define wxID_DEVICE_ID 3500 +#define wxID_RECORDING_PATH 8500 +#define wxID_RECORDING_SQUELCH_BASE 8501 +#define wxID_RECORDING_SQUELCH_SILENCE 8502 +#define wxID_RECORDING_SQUELCH_SKIP 8503 +#define wxID_RECORDING_SQUELCH_ALWAYS 8504 +#define wxID_RECORDING_FILE_TIME_LIMIT 8505 + #define wxID_AUDIO_BANDWIDTH_BASE 9000 #define wxID_AUDIO_DEVICE_MULTIPLIER 50 @@ -101,6 +108,11 @@ public: AppFrame(); ~AppFrame(); + wxMenu *makeFileMenu(); + + wxMenu *makeRecordingMenu(); + void updateRecordingMenu(); + void initDeviceParams(SDRDeviceInfo *devInfo); void updateDeviceParams(); @@ -119,6 +131,9 @@ public: int OnGlobalKeyDown(wxKeyEvent &event); int OnGlobalKeyUp(wxKeyEvent &event); + void toggleActiveDemodRecording(); + void toggleAllActiveDemodRecording(); + void setWaterfallLinesPerSecond(int lps); void setSpectrumAvgSpeed(double avg); @@ -171,6 +186,7 @@ private: bool actionOnMenuAudioSampleRate(wxCommandEvent& event); bool actionOnMenuDisplay(wxCommandEvent& event); bool actionOnMenuLoadSave(wxCommandEvent& event); + bool actionOnMenuRecording(wxCommandEvent& event); bool actionOnMenuRig(wxCommandEvent& event); wxString getSettingsLabel(const std::string& settingsName, @@ -205,6 +221,7 @@ private: std::vector devices; std::map inputDevices; std::map outputDevices; + std::map outputDeviceMenuItems; std::map sampleRateMenuItems; std::map antennaMenuItems; @@ -214,6 +231,10 @@ private: std::map settingsMenuItems; std::map audioSampleRateMenuItems; + + // + std::map recordingMenuItems; + std::map directSamplingMenuItems; wxMenuBar *menuBar; @@ -222,7 +243,9 @@ private: wxMenuItem *agcMenuItem = nullptr; wxMenuItem *iqSwapMenuItem = nullptr; wxMenuItem *lowPerfMenuItem = nullptr; + wxMenu *fileMenu = nullptr; wxMenu *settingsMenu = nullptr; + wxMenu *recordingMenu = nullptr; SoapySDR::ArgInfoList settingArgs; int settingsIdMax; diff --git a/src/BookmarkMgr.cpp b/src/BookmarkMgr.cpp index eec5d6f..85c4f86 100644 --- a/src/BookmarkMgr.cpp +++ b/src/BookmarkMgr.cpp @@ -415,7 +415,11 @@ bool BookmarkMgr::getExpandState(std::string groupName) { void BookmarkMgr::updateActiveList() { - std::lock_guard < std::recursive_mutex > lockData(busy_lock); + std::lock_guard < std::recursive_mutex > lockData(busy_lock); + + if (wxGetApp().isShuttingDown()) { + return; + } BookmarkView *bmv = wxGetApp().getAppFrame()->getBookmarkView(); diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp index aface75..e6b63b1 100644 --- a/src/CubicSDR.cpp +++ b/src/CubicSDR.cpp @@ -203,6 +203,7 @@ CubicSDR::CubicSDR() : frequency(0), offset(0), ppm(0), snap(1), sampleRate(DEFA sampleRateInitialized.store(false); agcMode.store(true); soloMode.store(false); + shuttingDown.store(false); fdlgTarget = FrequencyDialog::FDIALOG_TARGET_DEFAULT; stoppedDev = nullptr; } @@ -384,6 +385,8 @@ bool CubicSDR::OnInit() { } int CubicSDR::OnExit() { + shuttingDown.store(true); + #if USE_HAMLIB if (rigIsActive()) { std::cout << "Terminating Rig thread.." << std::endl << std::flush; @@ -1029,6 +1032,11 @@ bool CubicSDR::getSoloMode() { return soloMode.load(); } +bool CubicSDR::isShuttingDown() +{ + return shuttingDown.load(); +} + int CubicSDR::FilterEvent(wxEvent& event) { if (!appframe) { return -1; diff --git a/src/CubicSDR.h b/src/CubicSDR.h index 798089e..d097829 100644 --- a/src/CubicSDR.h +++ b/src/CubicSDR.h @@ -170,6 +170,8 @@ public: void setSoloMode(bool solo); bool getSoloMode(); + + bool isShuttingDown(); #ifdef USE_HAMLIB RigThread *getRigThread(); @@ -195,6 +197,7 @@ private: std::atomic_llong sampleRate; std::string antennaName; std::atomic_bool agcMode; + std::atomic_bool shuttingDown; SDRThread *sdrThread = nullptr; SDREnumerator *sdrEnum = nullptr; diff --git a/src/audio/AudioFile.cpp b/src/audio/AudioFile.cpp new file mode 100644 index 0000000..6b15db7 --- /dev/null +++ b/src/audio/AudioFile.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioFile.h" +#include "CubicSDR.h" +#include + +AudioFile::AudioFile() { + +} + +AudioFile::~AudioFile() { + +} + +void AudioFile::setOutputFileName(std::string filename) { + filenameBase = filename; +} + +std::string AudioFile::getOutputFileName() { + + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + + // Strip any invalid characters from the name + std::string stripChars("<>:\"/\\|?*"); + std::string filenameBaseSafe = filenameBase; + + for (size_t i = 0, iMax = filenameBaseSafe.length(); i < iMax; i++) { + if (stripChars.find(filenameBaseSafe[i]) != std::string::npos) { + filenameBaseSafe.replace(i,1,"_"); + } + } + + // Create output file name + std::stringstream outputFileName; + outputFileName << recPath << filePathSeparator << filenameBaseSafe; + + int idx = 0; + + // If the file exists; then find the next non-existing file in sequence. + std::string fileNameCandidate = outputFileName.str(); + + while (FILE *file = fopen((fileNameCandidate + "." + getExtension()).c_str(), "r")) { + fclose(file); + fileNameCandidate = outputFileName.str() + "-" + std::to_string(++idx); + } + + return fileNameCandidate + "." + getExtension(); +} + diff --git a/src/audio/AudioFile.h b/src/audio/AudioFile.h new file mode 100644 index 0000000..c8636b8 --- /dev/null +++ b/src/audio/AudioFile.h @@ -0,0 +1,25 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioThread.h" + +class AudioFile +{ + +public: + AudioFile(); + virtual ~AudioFile(); + + virtual void setOutputFileName(std::string filename); + virtual std::string getExtension() = 0; + virtual std::string getOutputFileName(); + + virtual bool writeToFile(AudioThreadInputPtr input) = 0; + virtual bool closeFile() = 0; + +protected: + std::string filenameBase; + +}; \ No newline at end of file diff --git a/src/audio/AudioFileWAV.cpp b/src/audio/AudioFileWAV.cpp new file mode 100644 index 0000000..d0629ab --- /dev/null +++ b/src/audio/AudioFileWAV.cpp @@ -0,0 +1,219 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioFileWAV.h" +#include "CubicSDR.h" +#include + +//limit file size to 2GB (- margin) for maximum compatibility. +#define MAX_WAV_FILE_SIZE (0x7FFFFFFF - 1024) + +// Simple endian io read/write handling from +// http://www.cplusplus.com/forum/beginner/31584/#msg171056 +namespace little_endian_io +{ + template + std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) { + for (; size; --size, value >>= 8) { + outs.put(static_cast (value & 0xFF)); + } + return outs; + } + + template + std::istream& read_word(std::istream& ins, Word& value, unsigned size = sizeof(Word)) { + for (unsigned n = 0, value = 0; n < size; ++n) { + value |= ins.get() << (8 * n); + } + return ins; + } +} + +namespace big_endian_io +{ + template + std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) { + while (size) { + outs.put(static_cast ((value >> (8 * --size)) & 0xFF)); + } + return outs; + } + + template + std::istream& read_word(std::istream& ins, Word& value, unsigned size = sizeof(Word)) { + for (value = 0; size; --size) { + value = (value << 8) | ins.get(); + } + return ins; + } +} + +using namespace little_endian_io; + +AudioFileWAV::AudioFileWAV() : AudioFile() { +} + +AudioFileWAV::~AudioFileWAV() { +} + + +std::string AudioFileWAV::getExtension() +{ + return "wav"; +} + +bool AudioFileWAV::writeToFile(AudioThreadInputPtr input) +{ + if (!outputFileStream.is_open()) { + + std::string ofName = getOutputFileName(); + + outputFileStream.open(ofName.c_str(), std::ios::binary); + currentFileSize = 0; + + writeHeaderToFileStream(input); + } + + size_t maxRoomInCurrentFileInSamples = getMaxWritableNumberOfSamples(input); + + if (maxRoomInCurrentFileInSamples >= input->data.size()) { + writePayloadToFileStream(input, 0, input->data.size()); + } + else { + //we complete the current file and open another: + writePayloadToFileStream(input, 0, maxRoomInCurrentFileInSamples); + + closeFile(); + + // Open a new file with the next sequence number, and dump the rest of samples in it. + currentSequenceNumber++; + currentFileSize = 0; + + std::string ofName = getOutputFileName(); + outputFileStream.open(ofName.c_str(), std::ios::binary); + + writeHeaderToFileStream(input); + writePayloadToFileStream(input, maxRoomInCurrentFileInSamples, input->data.size()); + } + + return true; +} + +bool AudioFileWAV::closeFile() +{ + if (outputFileStream.is_open()) { + size_t file_length = outputFileStream.tellp(); + + // Fix the data chunk header to contain the data size + outputFileStream.seekp(dataChunkPos + 4); + write_word(outputFileStream, file_length - dataChunkPos + 8); + + // Fix the file header to contain the proper RIFF chunk size, which is (file size - 8) bytes + outputFileStream.seekp(0 + 4); + write_word(outputFileStream, file_length - 8, 4); + + outputFileStream.close(); + currentFileSize = 0; + } + + return true; +} + +void AudioFileWAV::writeHeaderToFileStream(AudioThreadInputPtr input) { + + // Based on simple wav file output code from + // http://www.cplusplus.com/forum/beginner/166954/ + + // Write the wav file headers + outputFileStream << "RIFF----WAVEfmt "; // (chunk size to be filled in later) + write_word(outputFileStream, 16, 4); // no extension data + write_word(outputFileStream, 1, 2); // PCM - integer samples + write_word(outputFileStream, input->channels, 2); // channels + write_word(outputFileStream, input->sampleRate, 4); // samples per second (Hz) + write_word(outputFileStream, (input->sampleRate * 16 * input->channels) / 8, 4); // (Sample Rate * BitsPerSample * Channels) / 8 + write_word(outputFileStream, input->channels * 2, 2); // data block size (size of integer samples, one for each channel, in bytes) + write_word(outputFileStream, 16, 2); // number of bits per sample (use a multiple of 8) + + // Write the data chunk header + dataChunkPos = outputFileStream.tellp(); + currentFileSize = dataChunkPos; + outputFileStream << "data----"; // (chunk size to be filled in later) +} + +void AudioFileWAV::writePayloadToFileStream(AudioThreadInputPtr input, size_t startInputPosition, size_t endInputPosition) { + + // Prevent clipping + float intScale = (input->peak < 1.0) ? 32767.0f : (32767.0f / input->peak); + + if (input->channels == 1) { + for (size_t i = startInputPosition, iMax = endInputPosition; i < iMax; i++) { + + write_word(outputFileStream, int(input->data[i] * intScale), 2); + + currentFileSize += 2; + } + } + else if (input->channels == 2) { + for (size_t i = startInputPosition, iMax = endInputPosition / 2; i < iMax; i++) { + + write_word(outputFileStream, int(input->data[i * 2] * intScale), 2); + write_word(outputFileStream, int(input->data[i * 2 + 1] * intScale), 2); + + currentFileSize += 4; + } + } +} + +size_t AudioFileWAV::getMaxWritableNumberOfSamples(AudioThreadInputPtr input) { + + long long remainingBytesInFile = (long long)(MAX_WAV_FILE_SIZE) - currentFileSize; + + return (size_t)(remainingBytesInFile / (input->channels * 2)); + +} + +void AudioFileWAV::setOutputFileName(std::string filename) { + + if (filename != filenameBase) { + + currentSequenceNumber = 0; + } + + AudioFile::setOutputFileName(filename); +} + +std::string AudioFileWAV::getOutputFileName() { + + std::string recPath = wxGetApp().getConfig()->getRecordingPath(); + + // Strip any invalid characters from the name + std::string stripChars("<>:\"/\\|?*"); + std::string filenameBaseSafe = filenameBase; + + for (size_t i = 0, iMax = filenameBaseSafe.length(); i < iMax; i++) { + if (stripChars.find(filenameBaseSafe[i]) != std::string::npos) { + filenameBaseSafe.replace(i, 1, "_"); + } + } + + // Create output file name + std::stringstream outputFileName; + outputFileName << recPath << filePathSeparator << filenameBaseSafe; + + //customized part: append a sequence number. + if (currentSequenceNumber > 0) { + outputFileName << "_" << std::setfill('0') << std::setw(3) << currentSequenceNumber; + } + + int idx = 0; + + // If the file exists; then find the next non-existing file in sequence. + std::string fileNameCandidate = outputFileName.str(); + + while (FILE *file = fopen((fileNameCandidate + "." + getExtension()).c_str(), "r")) { + fclose(file); + fileNameCandidate = outputFileName.str() + "-" + std::to_string(++idx); + } + + return fileNameCandidate + "." + getExtension(); +} \ No newline at end of file diff --git a/src/audio/AudioFileWAV.h b/src/audio/AudioFileWAV.h new file mode 100644 index 0000000..1dc9672 --- /dev/null +++ b/src/audio/AudioFileWAV.h @@ -0,0 +1,42 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioFile.h" + +#include + +class AudioFileWAV : public AudioFile { + +public: + AudioFileWAV(); + ~AudioFileWAV(); + + //override to manage name change with multi-part WAV. + virtual void setOutputFileName(std::string filename); + + //override of the base method to generate multi-part + //WAV to overcome the WAV format size limit. + virtual std::string getOutputFileName(); + + virtual std::string getExtension(); + + virtual bool writeToFile(AudioThreadInputPtr input); + virtual bool closeFile(); + +protected: + std::ofstream outputFileStream; + size_t dataChunkPos; + long long currentFileSize = 0; + int currentSequenceNumber = 0; + +private: + + size_t getMaxWritableNumberOfSamples(AudioThreadInputPtr input); + + void writeHeaderToFileStream(AudioThreadInputPtr input); + + //write [startInputPosition; endInputPosition[ samples from input into the file. + void writePayloadToFileStream(AudioThreadInputPtr input, size_t startInputPosition, size_t endInputPosition); +}; \ No newline at end of file diff --git a/src/audio/AudioSinkFileThread.cpp b/src/audio/AudioSinkFileThread.cpp new file mode 100644 index 0000000..e49b57b --- /dev/null +++ b/src/audio/AudioSinkFileThread.cpp @@ -0,0 +1,140 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioSinkFileThread.h" +#include + +#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000) + +AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() { + +} + +AudioSinkFileThread::~AudioSinkFileThread() { + if (audioFileHandler != nullptr) { + audioFileHandler->closeFile(); + } +} + +void AudioSinkFileThread::sink(AudioThreadInputPtr input) { + if (!audioFileHandler) { + return; + } + + //by default, always write something + bool isSomethingToWrite = true; + + if (input->is_squelch_active) { + + if (squelchOption == SQUELCH_RECORD_SILENCE) { + + //patch with "silence" + input->data.assign(input->data.size(), 0.0f); + input->peak = 0.0f; + } + else if (squelchOption == SQUELCH_SKIP_SILENCE) { + isSomethingToWrite = false; + } + } + + //else, nothing to do record as if squelch was not enabled. + + if (!isSomethingToWrite) { + return; + } + + if (fileTimeLimit > 0) { + durationMeasurement.update(); + + //duration exeeded, close this file and create another + //with "now" as timestamp. + if (durationMeasurement.getSeconds() > fileTimeLimit) { + + audioFileHandler->closeFile(); + + //initialize the filename of the AudioFile with the current time + time_t t = std::time(nullptr); + tm ltm = *std::localtime(&t); + + // GCC 5+ + // fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); + + char timeStr[512]; + //International format: Year.Month.Day, also lexicographically sortable + strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", <m); + + audioFileHandler->setOutputFileName(fileNameBase + std::string("_") + timeStr); + + //reset duration counter + durationMeasurement.start(); + //the following writeToFile will take care of creating another file. + } + } + + // forward to output file handler + audioFileHandler->writeToFile(input); +} + +void AudioSinkFileThread::inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) { + // close, set new parameters, adjust file name sequence and re-open? + if (!audioFileHandler) { + return; + } + + audioFileHandler->closeFile(); + + //reset duration counter + durationMeasurement.start(); +} + +void AudioSinkFileThread::setAudioFileNameBase(const std::string& baseName) { + + fileNameBase = baseName; +} + +void AudioSinkFileThread::setAudioFileHandler(AudioFile * output) { + audioFileHandler = output; + + //initialize the filename of the AudioFile with the current time + time_t t = std::time(nullptr); + tm ltm = *std::localtime(&t); + + // GCC 5+ + // fileName << "_" << std::put_time(<m, "%d-%m-%Y_%H-%M-%S"); + + char timeStr[512]; + //International format: Year.Month.Day, also lexicographically sortable + strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", <m); + + audioFileHandler->setOutputFileName(fileNameBase + std::string("_") + timeStr); + + // reset Timer + durationMeasurement.start(); +} + +void AudioSinkFileThread::setSquelchOption(int squelchOptEnumValue) { + + if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_SILENCE) { + squelchOption = AudioSinkFileThread::SQUELCH_RECORD_SILENCE; + } + else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_SKIP_SILENCE) { + squelchOption = AudioSinkFileThread::SQUELCH_SKIP_SILENCE; + } + else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_ALWAYS) { + squelchOption = AudioSinkFileThread::SQUELCH_RECORD_ALWAYS; + } + else { + squelchOption = AudioSinkFileThread::SQUELCH_RECORD_SILENCE; + } +} + +// Time limit +void AudioSinkFileThread::setFileTimeLimit(int nbSeconds) { + + if (nbSeconds > 0) { + fileTimeLimit = nbSeconds; + } + else { + fileTimeLimit = 0; + } +} diff --git a/src/audio/AudioSinkFileThread.h b/src/audio/AudioSinkFileThread.h new file mode 100644 index 0000000..f0bdb73 --- /dev/null +++ b/src/audio/AudioSinkFileThread.h @@ -0,0 +1,50 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioSinkThread.h" +#include "AudioFile.h" +#include "Timer.h" + +class AudioSinkFileThread : public AudioSinkThread { + +public: + AudioSinkFileThread(); + ~AudioSinkFileThread(); + + enum SquelchOption { + SQUELCH_RECORD_SILENCE = 0, // default value, record as a user would hear it. + SQUELCH_SKIP_SILENCE = 1, // skip below-squelch level. + SQUELCH_RECORD_ALWAYS = 2, // record irrespective of the squelch level. + SQUELCH_RECORD_MAX + }; + + virtual void sink(AudioThreadInputPtr input); + virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps); + + void setAudioFileHandler(AudioFile *output); + + void setAudioFileNameBase(const std::string& baseName); + + //Squelch + void setSquelchOption(int squelchOptEnumValue); + + // Time limit + void setFileTimeLimit(int nbSeconds); + +protected: + + std::string fileNameBase; + + AudioFile *audioFileHandler = nullptr; + + SquelchOption squelchOption = SQUELCH_RECORD_SILENCE; + int fileTimeLimit = 0; + + int fileTimeDurationSeconds = -1; + + Timer durationMeasurement; + +}; + diff --git a/src/audio/AudioSinkThread.cpp b/src/audio/AudioSinkThread.cpp new file mode 100644 index 0000000..edf3152 --- /dev/null +++ b/src/audio/AudioSinkThread.cpp @@ -0,0 +1,54 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "AudioSinkThread.h" + +#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000) + +AudioSinkThread::AudioSinkThread() { + inputQueuePtr = std::make_shared(); + inputQueuePtr->set_max_num_items(1000); + setInputQueue("input", inputQueuePtr); +} + +AudioSinkThread::~AudioSinkThread() { + +} + +void AudioSinkThread::run() { +#ifdef __APPLE__ + pthread_t tID = pthread_self(); // ID of this thread + int priority = sched_get_priority_max(SCHED_RR) - 1; + sched_param prio = { priority }; // scheduling priority of thread + pthread_setschedparam(tID, SCHED_RR, &prio); +#endif + + AudioThreadInputPtr inp; + AudioThreadInput inputRef; + + while (!stopping) { + if (!inputQueuePtr->pop(inp, HEARTBEAT_CHECK_PERIOD_MICROS)) { + continue; + } + + if (inputRef.channels != inp->channels || + inputRef.frequency != inp->frequency || + inputRef.inputRate != inp->inputRate || + inputRef.sampleRate != inp->sampleRate) { + + inputChanged(inputRef, inp); + + inputRef.channels = inp->channels; + inputRef.frequency = inp->frequency; + inputRef.inputRate = inp->inputRate; + inputRef.sampleRate = inp->sampleRate; + } + + sink(inp); + } +} + +void AudioSinkThread::terminate() { + IOThread::terminate(); + inputQueuePtr->flush(); +} diff --git a/src/audio/AudioSinkThread.h b/src/audio/AudioSinkThread.h new file mode 100644 index 0000000..754c75d --- /dev/null +++ b/src/audio/AudioSinkThread.h @@ -0,0 +1,25 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "AudioThread.h" + +class AudioSinkThread : public IOThread { + +public: + + AudioSinkThread(); + virtual ~AudioSinkThread(); + + virtual void run(); + virtual void terminate(); + + virtual void sink(AudioThreadInputPtr input) = 0; + virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) = 0; + +protected: + std::recursive_mutex m_mutex; + AudioThreadInputQueuePtr inputQueuePtr; + +}; diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index de010d9..7585faa 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -21,13 +21,32 @@ public: int channels; float peak; int type; + bool is_squelch_active; + std::vector data; AudioThreadInput() : - frequency(0), sampleRate(0), channels(0), peak(0) { + frequency(0), inputRate(0), sampleRate(0), channels(0), peak(0), type(0), is_squelch_active(false) { } + + AudioThreadInput(AudioThreadInput *copyFrom) { + copy(copyFrom); + } + + void copy(AudioThreadInput *copyFrom) { + frequency = copyFrom->frequency; + inputRate = copyFrom->inputRate; + sampleRate = copyFrom->sampleRate; + channels = copyFrom->channels; + peak = copyFrom->peak; + type = copyFrom->type; + is_squelch_active = copyFrom->is_squelch_active; + data.assign(copyFrom->data.begin(), copyFrom->data.end()); + } + + virtual ~AudioThreadInput() { } diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 13f481b..39e9014 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -2,12 +2,15 @@ // SPDX-License-Identifier: GPL-2.0+ #include +#include + #include "DemodulatorInstance.h" #include "CubicSDR.h" #include "DemodulatorThread.h" #include "DemodulatorPreThread.h" - +#include "AudioSinkFileThread.h" +#include "AudioFileWAV.h" #if USE_HAMLIB #include "RigThread.h" @@ -47,6 +50,7 @@ DemodulatorInstance::DemodulatorInstance() { active.store(false); squelch.store(false); muted.store(false); + recording.store(false); deltaLock.store(false); deltaLockOfs.store(0); currentOutputDevice.store(-1); @@ -106,6 +110,7 @@ DemodulatorInstance::~DemodulatorInstance() { delete demodulatorPreThread; delete demodulatorThread; delete audioThread; + delete audioSinkThread; break; } @@ -181,6 +186,10 @@ void DemodulatorInstance::terminate() { // std::cout << "Terminating demodulator preprocessor thread.." << std::endl; demodulatorPreThread->terminate(); + if (audioSinkThread != nullptr) { + stopRecording(); + } + //that will actually unblock the currently blocked push(). pipeIQInputData->flush(); pipeAudioData->flush(); @@ -204,6 +213,7 @@ bool DemodulatorInstance::isTerminated() { bool audioTerminated = audioThread->isTerminated(); bool demodTerminated = demodulatorThread->isTerminated(); bool preDemodTerminated = demodulatorPreThread->isTerminated(); + bool audioSinkTerminated = (audioSinkThread == nullptr) || audioSinkThread->isTerminated(); //Cleanup the worker threads, if the threads are indeed terminated. // threads are linked as t_PreDemod ==> t_Demod ==> t_Audio @@ -240,14 +250,27 @@ bool DemodulatorInstance::isTerminated() { if (audioTerminated) { if (t_Audio) { +#ifdef __APPLE__ + pthread_join(t_PreDemod, NULL); +#else t_Audio->join(); - delete t_Audio; +#endif t_Audio = nullptr; } } - bool terminated = audioTerminated && demodTerminated && preDemodTerminated; + if (audioSinkTerminated) { + + if (t_AudioSink != nullptr) { + t_AudioSink->join(); + + delete t_AudioSink; + t_AudioSink = nullptr; + } + } + + bool terminated = audioTerminated && demodTerminated && preDemodTerminated && audioSinkTerminated; return terminated; } @@ -523,6 +546,21 @@ void DemodulatorInstance::setMuted(bool muted) { wxGetApp().getDemodMgr().setLastMuted(muted); } +bool DemodulatorInstance::isRecording() +{ + return recording.load(); +} + +void DemodulatorInstance::setRecording(bool recording_in) +{ + if (!recording.load() && recording_in) { + startRecording(); + } + else if (recording.load() && !recording_in) { + stopRecording(); + } +} + DemodVisualCue *DemodulatorInstance::getVisualCue() { return &visualCue; } @@ -580,6 +618,65 @@ ModemSettings DemodulatorInstance::getLastModemSettings(std::string demodType) { } } + +void DemodulatorInstance::startRecording() { + if (recording.load()) { + return; + } + + AudioSinkFileThread *newSinkThread = new AudioSinkFileThread(); + AudioFileWAV *afHandler = new AudioFileWAV(); + + std::stringstream fileName; + + std::wstring userLabel = getDemodulatorUserLabel(); + + wxString userLabelForFileName(userLabel); + std::string userLabelStr = userLabelForFileName.ToStdString(); + + if (!userLabelStr.empty()) { + fileName << userLabelStr; + } else { + fileName << getLabel(); + } + + newSinkThread->setAudioFileNameBase(fileName.str()); + + //attach options: + newSinkThread->setSquelchOption(wxGetApp().getConfig()->getRecordingSquelchOption()); + newSinkThread->setFileTimeLimit(wxGetApp().getConfig()->getRecordingFileTimeLimit()); + + newSinkThread->setAudioFileHandler(afHandler); + + audioSinkThread = newSinkThread; + t_AudioSink = new std::thread(&AudioSinkThread::threadMain, audioSinkThread); + + demodulatorThread->setOutputQueue("AudioSink", audioSinkThread->getInputQueue("input")); + + recording.store(true); +} + + +void DemodulatorInstance::stopRecording() { + if (!recording.load()) { + return; + } + + demodulatorThread->setOutputQueue("AudioSink", nullptr); + audioSinkThread->terminate(); + + t_AudioSink->join(); + + delete t_AudioSink; + delete audioSinkThread; + + t_AudioSink = nullptr; + audioSinkThread = nullptr; + + recording.store(false); +} + + #if ENABLE_DIGITAL_LAB ModemDigitalOutput *DemodulatorInstance::getOutput() { if (activeOutput == nullptr) { diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h index 846aa86..297df69 100644 --- a/src/demod/DemodulatorInstance.h +++ b/src/demod/DemodulatorInstance.h @@ -11,6 +11,7 @@ #include "ModemDigital.h" #include "ModemAnalog.h" #include "AudioThread.h" +#include "AudioSinkThread.h" #if ENABLE_DIGITAL_LAB #include "DigitalConsole.h" @@ -110,6 +111,9 @@ public: bool isMuted(); void setMuted(bool muted); + bool isRecording(); + void setRecording(bool recording); + DemodVisualCue *getVisualCue(); DemodulatorThreadInputQueuePtr getIQInputDataPipe(); @@ -131,6 +135,10 @@ public: void closeOutput(); #endif +protected: + void startRecording(); + void stopRecording(); + private: DemodulatorThreadInputQueuePtr pipeIQInputData; DemodulatorThreadPostInputQueuePtr pipeIQDemodData; @@ -139,6 +147,10 @@ private: DemodulatorThread *demodulatorThread; DemodulatorThreadControlCommandQueuePtr threadQueueControl; + AudioSinkThread *audioSinkThread = nullptr; + std::thread *t_AudioSink = nullptr; + AudioThreadInputQueuePtr audioSinkInputQueue; + //protects child thread creation and termination std::recursive_mutex m_thread_control_mutex; @@ -150,6 +162,8 @@ private: std::atomic_bool squelch; std::atomic_bool muted; std::atomic_bool deltaLock; + std::atomic_bool recording; + std::atomic_int deltaLockOfs; std::atomic_int currentOutputDevice; diff --git a/src/demod/DemodulatorMgr.cpp b/src/demod/DemodulatorMgr.cpp index 54f0f07..40978b9 100644 --- a/src/demod/DemodulatorMgr.cpp +++ b/src/demod/DemodulatorMgr.cpp @@ -58,7 +58,6 @@ void DemodulatorMgr::terminateAll() { DemodulatorInstancePtr d = demods.back(); demods.pop_back(); - wxGetApp().removeDemodulator(d); deleteThread(d); } } diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index d1bfb56..d828d26 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -43,6 +43,12 @@ void DemodulatorThread::onBindOutput(std::string name, ThreadQueueBasePtr thread audioVisOutputQueue = std::static_pointer_cast(threadQueue); } + + if (name == "AudioSink") { + std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); + + audioSinkOutputQueue = std::static_pointer_cast(threadQueue); + } } double DemodulatorThread::abMagnitude(float inphase, float quadrature) { @@ -195,7 +201,7 @@ void DemodulatorThread::run() { signalLevel = signalLevel + (currentSignalLevel - signalLevel) * 0.05 * sampleTime * 30.0; } - bool squelched = (muted.load() || (squelchEnabled && (signalLevel < squelchLevel))); + bool squelched = squelchEnabled && (signalLevel < squelchLevel); if (squelchEnabled) { if (!squelched && !squelchBreak) { @@ -218,20 +224,25 @@ void DemodulatorThread::run() { squelchBreak = false; } } - - if (audioOutputQueue != nullptr && ati && ati->data.size() && !squelched) { - std::vector::iterator data_i; - ati->peak = 0; - for (auto data_i : ati->data) { - float p = fabs(data_i); - if (p > ati->peak) { - ati->peak = p; - } - } - } else if (ati) { - ati = nullptr; - } - + + //compute audio peak: + if (audioOutputQueue != nullptr && ati) { + + ati->peak = 0; + + for (auto data_i : ati->data) { + float p = fabs(data_i); + if (p > ati->peak) { + ati->peak = p; + } + } + } + + //attach squelch flag to samples, to be used by audio sink. + if (ati) { + ati->is_squelch_active = squelched; + } + //At that point, capture the current state of audioVisOutputQueue in a local //variable, and works with it with now on until the next while-turn. DemodulatorThreadOutputQueuePtr localAudioVisOutputQueue = nullptr; @@ -240,7 +251,7 @@ void DemodulatorThread::run() { localAudioVisOutputQueue = audioVisOutputQueue; } - if ((ati || modemDigital) && localAudioVisOutputQueue != nullptr && localAudioVisOutputQueue->empty()) { + if (!squelched && (ati || modemDigital) && localAudioVisOutputQueue != nullptr && localAudioVisOutputQueue->empty()) { AudioThreadInputPtr ati_vis = std::make_shared(); @@ -310,7 +321,7 @@ void DemodulatorThread::run() { } } - if (ati != nullptr) { + if (!squelched && ati != nullptr) { if (!muted.load() && (!wxGetApp().getSoloMode() || (demodInstance == wxGetApp().getDemodMgr().getLastActiveDemodulator().get()))) { //non-blocking push needed for audio out if (!audioOutputQueue->try_push(ati)) { @@ -321,6 +332,23 @@ void DemodulatorThread::run() { } } + + // Capture audioSinkOutputQueue state in a local variable + DemodulatorThreadOutputQueuePtr localAudioSinkOutputQueue = nullptr; + { + std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue); + localAudioSinkOutputQueue = audioSinkOutputQueue; + } + + //Push to audio sink, if any: + if (ati && localAudioSinkOutputQueue != nullptr) { + + if (!localAudioSinkOutputQueue->try_push(ati)) { + std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl; + std::this_thread::yield(); + } + } + DemodulatorThreadControlCommand command; //empty command queue, execute commands @@ -398,4 +426,4 @@ void DemodulatorThread::releaseSquelchLock(DemodulatorInstance* inst) { if (inst == nullptr || squelchLock == inst) { squelchLock = nullptr; } -} \ No newline at end of file +} diff --git a/src/demod/DemodulatorThread.h b/src/demod/DemodulatorThread.h index 836866c..45607bb 100644 --- a/src/demod/DemodulatorThread.h +++ b/src/demod/DemodulatorThread.h @@ -67,6 +67,8 @@ protected: DemodulatorThreadOutputQueuePtr audioVisOutputQueue; DemodulatorThreadControlCommandQueuePtr threadQueueControl; + DemodulatorThreadOutputQueuePtr audioSinkOutputQueue = nullptr; + //protects the audioVisOutputQueue dynamic binding change at runtime (in DemodulatorMgr) std::mutex m_mutexAudioVisOutputQueue; }; diff --git a/src/forms/Bookmark/BookmarkView.cpp b/src/forms/Bookmark/BookmarkView.cpp index f4c316e..ee84b13 100644 --- a/src/forms/Bookmark/BookmarkView.cpp +++ b/src/forms/Bookmark/BookmarkView.cpp @@ -821,8 +821,16 @@ void BookmarkView::activeSelection(DemodulatorInstancePtr dsel) { clearButtons(); addBookmarkChoice(m_buttonPanel); - addButton(m_buttonPanel, "Remove Active", wxCommandEventHandler( BookmarkView::onRemoveActive )); + + if (dsel->isActive() && !(dsel->isRecording())) { + addButton(m_buttonPanel, "Start Recording", wxCommandEventHandler( BookmarkView::onStartRecording )); + } else { + addButton(m_buttonPanel, "Stop Recording", wxCommandEventHandler( BookmarkView::onStopRecording )); + } + addButton(m_buttonPanel, "Remove Active", wxCommandEventHandler( BookmarkView::onRemoveActive )); + + showProps(); showButtons(); refreshLayout(); @@ -1158,6 +1166,30 @@ void BookmarkView::onRemoveActive( wxCommandEvent& /* event */ ) { } } +void BookmarkView::onStartRecording( wxCommandEvent& /* event */ ) { + TreeViewItem *curSel = itemToTVI(m_treeView->GetSelection()); + + if (curSel && curSel->type == TreeViewItem::TREEVIEW_ITEM_TYPE_ACTIVE) { + if (!curSel->demod->isRecording() && wxGetApp().getConfig()->verifyRecordingPath()) { + curSel->demod->setRecording(true); + wxGetApp().getBookmarkMgr().updateActiveList(); + } + } +} + + +void BookmarkView::onStopRecording( wxCommandEvent& /* event */ ) { + TreeViewItem *curSel = itemToTVI(m_treeView->GetSelection()); + + if (curSel && curSel->type == TreeViewItem::TREEVIEW_ITEM_TYPE_ACTIVE) { + if (curSel->demod->isRecording()) { + curSel->demod->setRecording(false); + wxGetApp().getBookmarkMgr().updateActiveList(); + } + } +} + + void BookmarkView::onRemoveBookmark( wxCommandEvent& /* event */ ) { if (editingLabel) { diff --git a/src/forms/Bookmark/BookmarkView.h b/src/forms/Bookmark/BookmarkView.h index 8ed1bff..820ece1 100644 --- a/src/forms/Bookmark/BookmarkView.h +++ b/src/forms/Bookmark/BookmarkView.h @@ -145,6 +145,8 @@ protected: void onBookmarkChoice( wxCommandEvent &event ); void onRemoveActive( wxCommandEvent& event ); + void onStartRecording( wxCommandEvent& event ); + void onStopRecording( wxCommandEvent& event ); void onRemoveBookmark( wxCommandEvent& event ); void onActivateBookmark( wxCommandEvent& event ); diff --git a/src/forms/Dialog/AboutDialog.fbp b/src/forms/Dialog/AboutDialog.fbp index 9f9cb8a..3d91594 100644 --- a/src/forms/Dialog/AboutDialog.fbp +++ b/src/forms/Dialog/AboutDialog.fbp @@ -6534,6 +6534,421 @@ + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Chad Myslinsky + + 0 + + + 0 + + 1 + m_dChadMyslinsky + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Charlie Bruckner + + 0 + + + 0 + + 1 + m_dCharlieBruckner + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Jordan Parker + + 0 + + + 0 + + 1 + m_dJordanParker + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Robert Chave + + 0 + + + 0 + + 1 + m_dRobertChave + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Marvin Calvert + + 0 + + + 0 + + 1 + m_dMarvinCalvert + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/forms/Dialog/AboutDialogBase.cpp b/src/forms/Dialog/AboutDialogBase.cpp index 84bb003..fd26f24 100644 --- a/src/forms/Dialog/AboutDialogBase.cpp +++ b/src/forms/Dialog/AboutDialogBase.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 27 2017) +// C++ code generated with wxFormBuilder (version Nov 6 2017) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -9,475 +9,495 @@ /////////////////////////////////////////////////////////////////////////// -AboutDialogBase::AboutDialogBase(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxDialog(parent, id, title, pos, size, style) +AboutDialogBase::AboutDialogBase( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { - this->SetSizeHints(wxDefaultSize, wxDefaultSize); - + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + wxBoxSizer* dlgSizer; - dlgSizer = new wxBoxSizer(wxVERTICAL); - - m_hPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + dlgSizer = new wxBoxSizer( wxVERTICAL ); + + m_hPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxBoxSizer* m_hSizer; - m_hSizer = new wxBoxSizer(wxHORIZONTAL); - - m_appName = new wxStaticText(m_hPanel, wxID_ANY, wxT("CubicSDR"), wxDefaultPosition, wxDefaultSize, 0); - m_appName->Wrap(-1); - m_appName->SetFont(wxFont(20, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_hSizer->Add(m_appName, 0, wxALL, 6); - - - m_hPanel->SetSizer(m_hSizer); + m_hSizer = new wxBoxSizer( wxHORIZONTAL ); + + m_appName = new wxStaticText( m_hPanel, wxID_ANY, wxT("CubicSDR"), wxDefaultPosition, wxDefaultSize, 0 ); + m_appName->Wrap( -1 ); + m_appName->SetFont( wxFont( 20, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_hSizer->Add( m_appName, 0, wxALL, 6 ); + + + m_hPanel->SetSizer( m_hSizer ); m_hPanel->Layout(); - m_hSizer->Fit(m_hPanel); - dlgSizer->Add(m_hPanel, 0, wxALL | wxEXPAND, 5); - - m_aboutNotebook = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); - m_dbScroll = new wxScrolledWindow(m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL); - m_dbScroll->SetScrollRate(5, 5); + m_hSizer->Fit( m_hPanel ); + dlgSizer->Add( m_hPanel, 0, wxALL|wxEXPAND, 5 ); + + m_aboutNotebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_dbScroll = new wxScrolledWindow( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_dbScroll->SetScrollRate( 5, 5 ); wxBoxSizer* m_dbPane; - m_dbPane = new wxBoxSizer(wxVERTICAL); - + m_dbPane = new wxBoxSizer( wxVERTICAL ); + wxFlexGridSizer* m_dbSizer; - m_dbSizer = new wxFlexGridSizer(0, 3, 2, 20); - m_dbSizer->SetFlexibleDirection(wxBOTH); - m_dbSizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); - - m_dbHeader = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Developed By"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_dbHeader->Wrap(-1); - m_dbHeader->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_dbSizer->Add(m_dbHeader, 0, wxALL, 5); - - m_dbGHHeader = new wxStaticText(m_dbScroll, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0); - m_dbGHHeader->Wrap(-1); - m_dbGHHeader->SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_dbSizer->Add(m_dbGHHeader, 0, wxALL, 5); - - m_dbTwitter = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Twitter"), wxDefaultPosition, wxDefaultSize, 0); - m_dbTwitter->Wrap(-1); - m_dbTwitter->SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_dbSizer->Add(m_dbTwitter, 0, wxALL, 5); - - m_dbCharlesCliffe = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Charles J. Cliffe"), wxDefaultPosition, wxDefaultSize, 0); - m_dbCharlesCliffe->Wrap(-1); - m_dbSizer->Add(m_dbCharlesCliffe, 0, wxALL, 5); - - m_dbghCC = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@cjcliffe"), wxDefaultPosition, wxDefaultSize, 0); - m_dbghCC->Wrap(-1); - m_dbSizer->Add(m_dbghCC, 0, wxALL, 5); - - m_dbtCC = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@ccliffe"), wxDefaultPosition, wxDefaultSize, 0); - m_dbtCC->Wrap(-1); - m_dbSizer->Add(m_dbtCC, 0, wxALL, 5); - - m_dbVincentSonnier = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0); - m_dbVincentSonnier->Wrap(-1); - m_dbSizer->Add(m_dbVincentSonnier, 0, wxALL, 5); - - m_dbghVS = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@vsonnier"), wxDefaultPosition, wxDefaultSize, 0); - m_dbghVS->Wrap(-1); - m_dbSizer->Add(m_dbghVS, 0, wxALL, 5); - - m_dbtVS = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@VincentSonnier"), wxDefaultPosition, wxDefaultSize, 0); - m_dbtVS->Wrap(-1); - m_dbSizer->Add(m_dbtVS, 0, wxALL, 5); - - - m_dbPane->Add(m_dbSizer, 0, wxALL | wxEXPAND, 5); - - m_dbDivider1 = new wxStaticLine(m_dbScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_dbPane->Add(m_dbDivider1, 0, wxALL | wxEXPAND, 10); - + m_dbSizer = new wxFlexGridSizer( 0, 3, 2, 20 ); + m_dbSizer->SetFlexibleDirection( wxBOTH ); + m_dbSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL ); + + m_dbHeader = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Developed By"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_dbHeader->Wrap( -1 ); + m_dbHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_dbSizer->Add( m_dbHeader, 0, wxALL, 5 ); + + m_dbGHHeader = new wxStaticText( m_dbScroll, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbGHHeader->Wrap( -1 ); + m_dbGHHeader->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_dbSizer->Add( m_dbGHHeader, 0, wxALL, 5 ); + + m_dbTwitter = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Twitter"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbTwitter->Wrap( -1 ); + m_dbTwitter->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_dbSizer->Add( m_dbTwitter, 0, wxALL, 5 ); + + m_dbCharlesCliffe = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Charles J. Cliffe"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbCharlesCliffe->Wrap( -1 ); + m_dbSizer->Add( m_dbCharlesCliffe, 0, wxALL, 5 ); + + m_dbghCC = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@cjcliffe"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbghCC->Wrap( -1 ); + m_dbSizer->Add( m_dbghCC, 0, wxALL, 5 ); + + m_dbtCC = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@ccliffe"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbtCC->Wrap( -1 ); + m_dbSizer->Add( m_dbtCC, 0, wxALL, 5 ); + + m_dbVincentSonnier = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbVincentSonnier->Wrap( -1 ); + m_dbSizer->Add( m_dbVincentSonnier, 0, wxALL, 5 ); + + m_dbghVS = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@vsonnier"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbghVS->Wrap( -1 ); + m_dbSizer->Add( m_dbghVS, 0, wxALL, 5 ); + + m_dbtVS = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@VincentSonnier"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dbtVS->Wrap( -1 ); + m_dbSizer->Add( m_dbtVS, 0, wxALL, 5 ); + + + m_dbPane->Add( m_dbSizer, 0, wxALL|wxEXPAND, 5 ); + + m_dbDivider1 = new wxStaticLine( m_dbScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_dbPane->Add( m_dbDivider1, 0, wxALL|wxEXPAND, 10 ); + wxFlexGridSizer* m_cSizer; - m_cSizer = new wxFlexGridSizer(0, 2, 2, 20); - m_cSizer->SetFlexibleDirection(wxBOTH); - m_cSizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); - - m_cContributorsHeader = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Contributors"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_cContributorsHeader->Wrap(-1); - m_cContributorsHeader->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_cSizer->Add(m_cContributorsHeader, 0, wxALL, 5); - - m_cGitHub = new wxStaticText(m_dbScroll, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0); - m_cGitHub->Wrap(-1); - m_cGitHub->SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_cSizer->Add(m_cGitHub, 0, wxALL, 5); - - m_cCorneLukken = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Corne Lukken"), wxDefaultPosition, wxDefaultSize, 0); - m_cCorneLukken->Wrap(-1); - m_cSizer->Add(m_cCorneLukken, 0, wxALL, 5); - - m_cghCL = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@Dantali0n"), wxDefaultPosition, wxDefaultSize, 0); - m_cghCL->Wrap(-1); - m_cSizer->Add(m_cghCL, 0, wxALL, 5); - - m_cStainislawPitucha = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Stanisław Pitucha"), wxDefaultPosition, wxDefaultSize, 0); - m_cStainislawPitucha->Wrap(-1); - m_cSizer->Add(m_cStainislawPitucha, 0, wxALL, 5); - - m_cghSP = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@viraptor"), wxDefaultPosition, wxDefaultSize, 0); - m_cghSP->Wrap(-1); - m_cSizer->Add(m_cghSP, 0, wxALL, 5); - - m_cghStefanTalpalaru = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Ștefan Talpalaru"), wxDefaultPosition, wxDefaultSize, 0); - m_cghStefanTalpalaru->Wrap(-1); - m_cSizer->Add(m_cghStefanTalpalaru, 0, wxALL, 5); - - m_cghST = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@stefantalpalaru"), wxDefaultPosition, wxDefaultSize, 0); - m_cghST->Wrap(-1); - m_cSizer->Add(m_cghST, 0, wxALL, 5); - - m_cCrisMotch = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Chris Motch"), wxDefaultPosition, wxDefaultSize, 0); - m_cCrisMotch->Wrap(-1); - m_cSizer->Add(m_cCrisMotch, 0, wxALL, 5); - - m_cghCM = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@bodrick"), wxDefaultPosition, wxDefaultSize, 0); - m_cghCM->Wrap(-1); - m_cSizer->Add(m_cghCM, 0, wxALL, 5); - - m_cMariuszRyndzionek = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Mariusz Ryndzionek"), wxDefaultPosition, wxDefaultSize, 0); - m_cMariuszRyndzionek->Wrap(-1); - m_cSizer->Add(m_cMariuszRyndzionek, 0, wxALL, 5); - - m_cghMR = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@mryndzionek"), wxDefaultPosition, wxDefaultSize, 0); - m_cghMR->Wrap(-1); - m_cSizer->Add(m_cghMR, 0, wxALL, 5); - - m_cJiangWei = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Jiang Wei"), wxDefaultPosition, wxDefaultSize, 0); - m_cJiangWei->Wrap(-1); - m_cSizer->Add(m_cJiangWei, 0, wxALL, 5); - - m_cghJW = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@jocover"), wxDefaultPosition, wxDefaultSize, 0); - m_cghJW->Wrap(-1); - m_cSizer->Add(m_cghJW, 0, wxALL, 5); - - m_cTomSwartz = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Tom Swartz"), wxDefaultPosition, wxDefaultSize, 0); - m_cTomSwartz->Wrap(-1); - m_cSizer->Add(m_cTomSwartz, 0, wxALL, 5); - - m_cghTS = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@tomswartz07"), wxDefaultPosition, wxDefaultSize, 0); - m_cghTS->Wrap(-1); - m_cSizer->Add(m_cghTS, 0, wxALL, 5); - - m_cInfinityCyberworks = new wxStaticText(m_dbScroll, wxID_ANY, wxT("Infinity Cyberworks"), wxDefaultPosition, wxDefaultSize, 0); - m_cInfinityCyberworks->Wrap(-1); - m_cSizer->Add(m_cInfinityCyberworks, 0, wxALL, 5); - - m_cghIC = new wxStaticText(m_dbScroll, wxID_ANY, wxT("@infinitycyberworks"), wxDefaultPosition, wxDefaultSize, 0); - m_cghIC->Wrap(-1); - m_cSizer->Add(m_cghIC, 0, wxALL, 5); - - - m_dbPane->Add(m_cSizer, 0, wxALL | wxEXPAND, 5); - - - m_dbScroll->SetSizer(m_dbPane); + m_cSizer = new wxFlexGridSizer( 0, 2, 2, 20 ); + m_cSizer->SetFlexibleDirection( wxBOTH ); + m_cSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL ); + + m_cContributorsHeader = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Contributors"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_cContributorsHeader->Wrap( -1 ); + m_cContributorsHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_cSizer->Add( m_cContributorsHeader, 0, wxALL, 5 ); + + m_cGitHub = new wxStaticText( m_dbScroll, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cGitHub->Wrap( -1 ); + m_cGitHub->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_cSizer->Add( m_cGitHub, 0, wxALL, 5 ); + + m_cCorneLukken = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Corne Lukken"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cCorneLukken->Wrap( -1 ); + m_cSizer->Add( m_cCorneLukken, 0, wxALL, 5 ); + + m_cghCL = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@Dantali0n"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghCL->Wrap( -1 ); + m_cSizer->Add( m_cghCL, 0, wxALL, 5 ); + + m_cStainislawPitucha = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Stanisław Pitucha"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cStainislawPitucha->Wrap( -1 ); + m_cSizer->Add( m_cStainislawPitucha, 0, wxALL, 5 ); + + m_cghSP = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@viraptor"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghSP->Wrap( -1 ); + m_cSizer->Add( m_cghSP, 0, wxALL, 5 ); + + m_cghStefanTalpalaru = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Ștefan Talpalaru"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghStefanTalpalaru->Wrap( -1 ); + m_cSizer->Add( m_cghStefanTalpalaru, 0, wxALL, 5 ); + + m_cghST = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@stefantalpalaru"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghST->Wrap( -1 ); + m_cSizer->Add( m_cghST, 0, wxALL, 5 ); + + m_cCrisMotch = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Chris Motch"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cCrisMotch->Wrap( -1 ); + m_cSizer->Add( m_cCrisMotch, 0, wxALL, 5 ); + + m_cghCM = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@bodrick"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghCM->Wrap( -1 ); + m_cSizer->Add( m_cghCM, 0, wxALL, 5 ); + + m_cMariuszRyndzionek = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Mariusz Ryndzionek"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cMariuszRyndzionek->Wrap( -1 ); + m_cSizer->Add( m_cMariuszRyndzionek, 0, wxALL, 5 ); + + m_cghMR = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@mryndzionek"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghMR->Wrap( -1 ); + m_cSizer->Add( m_cghMR, 0, wxALL, 5 ); + + m_cJiangWei = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Jiang Wei"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cJiangWei->Wrap( -1 ); + m_cSizer->Add( m_cJiangWei, 0, wxALL, 5 ); + + m_cghJW = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@jocover"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghJW->Wrap( -1 ); + m_cSizer->Add( m_cghJW, 0, wxALL, 5 ); + + m_cTomSwartz = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Tom Swartz"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cTomSwartz->Wrap( -1 ); + m_cSizer->Add( m_cTomSwartz, 0, wxALL, 5 ); + + m_cghTS = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@tomswartz07"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghTS->Wrap( -1 ); + m_cSizer->Add( m_cghTS, 0, wxALL, 5 ); + + m_cInfinityCyberworks = new wxStaticText( m_dbScroll, wxID_ANY, wxT("Infinity Cyberworks"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cInfinityCyberworks->Wrap( -1 ); + m_cSizer->Add( m_cInfinityCyberworks, 0, wxALL, 5 ); + + m_cghIC = new wxStaticText( m_dbScroll, wxID_ANY, wxT("@infinitycyberworks"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cghIC->Wrap( -1 ); + m_cSizer->Add( m_cghIC, 0, wxALL, 5 ); + + + m_dbPane->Add( m_cSizer, 0, wxALL|wxEXPAND, 5 ); + + + m_dbScroll->SetSizer( m_dbPane ); m_dbScroll->Layout(); - m_dbPane->Fit(m_dbScroll); - m_aboutNotebook->AddPage(m_dbScroll, wxT("Developers"), false); - m_dScroll = new wxScrolledWindow(m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL); - m_dScroll->SetScrollRate(5, 5); + m_dbPane->Fit( m_dbScroll ); + m_aboutNotebook->AddPage( m_dbScroll, wxT("Developers"), false ); + m_dScroll = new wxScrolledWindow( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_dScroll->SetScrollRate( 5, 5 ); wxBoxSizer* m_dBSizer; - m_dBSizer = new wxBoxSizer(wxVERTICAL); - + m_dBSizer = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* m_dSizer; - m_dSizer = new wxBoxSizer(wxVERTICAL); - - m_dHeader = new wxStaticText(m_dScroll, wxID_ANY, wxT("Thanks to everyone who donated at cubicsdr.com!"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_dHeader->Wrap(-1); - m_dHeader->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_dSizer->Add(m_dHeader, 0, wxALL, 5); - - m_dDivider1 = new wxStaticLine(m_dScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_dSizer->Add(m_dDivider1, 0, wxEXPAND | wxALL, 5); - - m_dSDRplay = new wxStaticText(m_dScroll, wxID_ANY, wxT("SDRplay / sdrplay.com"), wxDefaultPosition, wxDefaultSize, 0); - m_dSDRplay->Wrap(-1); - m_dSizer->Add(m_dSDRplay, 0, wxALL, 5); - - m_dMichaelLadd = new wxStaticText(m_dScroll, wxID_ANY, wxT("Michael Ladd"), wxDefaultPosition, wxDefaultSize, 0); - m_dMichaelLadd->Wrap(-1); - m_dSizer->Add(m_dMichaelLadd, 0, wxALL, 5); - - m_dAutoMotiveTemplates = new wxStaticText(m_dScroll, wxID_ANY, wxT("Automotive Templates"), wxDefaultPosition, wxDefaultSize, 0); - m_dAutoMotiveTemplates->Wrap(-1); - m_dSizer->Add(m_dAutoMotiveTemplates, 0, wxALL, 5); - - m_dJorgeMorales = new wxStaticText(m_dScroll, wxID_ANY, wxT("Jorge Morales"), wxDefaultPosition, wxDefaultSize, 0); - m_dJorgeMorales->Wrap(-1); - m_dSizer->Add(m_dJorgeMorales, 0, wxALL, 5); - - m_dMichaelRooke = new wxStaticText(m_dScroll, wxID_ANY, wxT("Michael Rooke"), wxDefaultPosition, wxDefaultSize, 0); - m_dMichaelRooke->Wrap(-1); - m_dSizer->Add(m_dMichaelRooke, 0, wxALL, 5); - - m_dTNCOM = new wxStaticText(m_dScroll, wxID_ANY, wxT("TNCOM"), wxDefaultPosition, wxDefaultSize, 0); - m_dTNCOM->Wrap(-1); - m_dSizer->Add(m_dTNCOM, 0, wxALL, 5); - - m_dErikWied = new wxStaticText(m_dScroll, wxID_ANY, wxT("Erik Mikkel Wied"), wxDefaultPosition, wxDefaultSize, 0); - m_dErikWied->Wrap(-1); - m_dSizer->Add(m_dErikWied, 0, wxALL, 5); - - m_dRobertDuering = new wxStaticText(m_dScroll, wxID_ANY, wxT("Robert Duering"), wxDefaultPosition, wxDefaultSize, 0); - m_dRobertDuering->Wrap(-1); - m_dSizer->Add(m_dRobertDuering, 0, wxALL, 5); - - m_dJimDeitch = new wxStaticText(m_dScroll, wxID_ANY, wxT("Jim Deitch"), wxDefaultPosition, wxDefaultSize, 0); - m_dJimDeitch->Wrap(-1); - m_dSizer->Add(m_dJimDeitch, 0, wxALL, 5); - - m_dNooElec = new wxStaticText(m_dScroll, wxID_ANY, wxT("NooElec Inc. / nooelec.com"), wxDefaultPosition, wxDefaultSize, 0); - m_dNooElec->Wrap(-1); - m_dSizer->Add(m_dNooElec, 0, wxALL, 5); - - m_dDavidAhlgren = new wxStaticText(m_dScroll, wxID_ANY, wxT("David Ahlgren"), wxDefaultPosition, wxDefaultSize, 0); - m_dDavidAhlgren->Wrap(-1); - m_dSizer->Add(m_dDavidAhlgren, 0, wxALL, 5); - - m_dRonaldCook = new wxStaticText(m_dScroll, wxID_ANY, wxT("Ronald Cook"), wxDefaultPosition, wxDefaultSize, 0); - m_dRonaldCook->Wrap(-1); - m_dSizer->Add(m_dRonaldCook, 0, wxALL, 5); - - m_dEricPeterson = new wxStaticText(m_dScroll, wxID_ANY, wxT("Eric Peterson"), wxDefaultPosition, wxDefaultSize, 0); - m_dEricPeterson->Wrap(-1); - m_dSizer->Add(m_dEricPeterson, 0, wxALL, 5); - - m_dGeoDistributing = new wxStaticText(m_dScroll, wxID_ANY, wxT("Geo Distributing"), wxDefaultPosition, wxDefaultSize, 0); - m_dGeoDistributing->Wrap(-1); - m_dSizer->Add(m_dGeoDistributing, 0, wxALL, 5); - - m_dJamesCarson = new wxStaticText(m_dScroll, wxID_ANY, wxT("James Carson"), wxDefaultPosition, wxDefaultSize, 0); - m_dJamesCarson->Wrap(-1); - m_dSizer->Add(m_dJamesCarson, 0, wxALL, 5); - - m_dCraigWilliams = new wxStaticText(m_dScroll, wxID_ANY, wxT("Craig Williams"), wxDefaultPosition, wxDefaultSize, 0); - m_dCraigWilliams->Wrap(-1); - m_dSizer->Add(m_dCraigWilliams, 0, wxALL, 5); - - m_dRudolfShaffer = new wxStaticText(m_dScroll, wxID_ANY, wxT("Rudolf Schaffer"), wxDefaultPosition, wxDefaultSize, 0); - m_dRudolfShaffer->Wrap(-1); - m_dSizer->Add(m_dRudolfShaffer, 0, wxALL, 5); - - m_dJohnKaton = new wxStaticText(m_dScroll, wxID_ANY, wxT("John Katon"), wxDefaultPosition, wxDefaultSize, 0); - m_dJohnKaton->Wrap(-1); - m_dSizer->Add(m_dJohnKaton, 0, wxALL, 5); - - m_dVincentSonnier = new wxStaticText(m_dScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0); - m_dVincentSonnier->Wrap(-1); - m_dSizer->Add(m_dVincentSonnier, 0, wxALL, 5); - - m_dCorq = new wxStaticText(m_dScroll, wxID_ANY, wxT("corq's auctions/L. Easterly LTD (x 4)"), wxDefaultPosition, wxDefaultSize, 0); - m_dCorq->Wrap(-1); - m_dSizer->Add(m_dCorq, 0, wxALL, 5); - - m_dIvanAlekseev = new wxStaticText(m_dScroll, wxID_ANY, wxT("Ivan Alekseev"), wxDefaultPosition, wxDefaultSize, 0); - m_dIvanAlekseev->Wrap(-1); - m_dSizer->Add(m_dIvanAlekseev, 0, wxALL, 5); - - m_dOleJorgenKolsrud = new wxStaticText(m_dScroll, wxID_ANY, wxT("Ole-Jørgen Næss Kolsrud"), wxDefaultPosition, wxDefaultSize, 0); - m_dOleJorgenKolsrud->Wrap(-1); - m_dSizer->Add(m_dOleJorgenKolsrud, 0, wxALL, 5); - - m_dHenrikJagemyr = new wxStaticText(m_dScroll, wxID_ANY, wxT("Henrik Jagemyr"), wxDefaultPosition, wxDefaultSize, 0); - m_dHenrikJagemyr->Wrap(-1); - m_dSizer->Add(m_dHenrikJagemyr, 0, wxALL, 5); - - m_dPeterHaines = new wxStaticText(m_dScroll, wxID_ANY, wxT("Peter Haines"), wxDefaultPosition, wxDefaultSize, 0); - m_dPeterHaines->Wrap(-1); - m_dSizer->Add(m_dPeterHaines, 0, wxALL, 5); - - m_dLeonAbrassart = new wxStaticText(m_dScroll, wxID_ANY, wxT("Leon Abrassart"), wxDefaultPosition, wxDefaultSize, 0); - m_dLeonAbrassart->Wrap(-1); - m_dSizer->Add(m_dLeonAbrassart, 0, wxALL, 5); - - m_dGeorgeTalbot = new wxStaticText(m_dScroll, wxID_ANY, wxT("George Alan Talbot"), wxDefaultPosition, wxDefaultSize, 0); - m_dGeorgeTalbot->Wrap(-1); - m_dSizer->Add(m_dGeorgeTalbot, 0, wxALL, 5); - - m_dFranciscoPuerta = new wxStaticText(m_dScroll, wxID_ANY, wxT("Francisco Borja Marcos de la Puerta"), wxDefaultPosition, wxDefaultSize, 0); - m_dFranciscoPuerta->Wrap(-1); - m_dSizer->Add(m_dFranciscoPuerta, 0, wxALL, 5); - - m_dRonaldLundeen = new wxStaticText(m_dScroll, wxID_ANY, wxT("Ronald A. Lundeen"), wxDefaultPosition, wxDefaultSize, 0); - m_dRonaldLundeen->Wrap(-1); - m_dSizer->Add(m_dRonaldLundeen, 0, wxALL, 5); - - m_dWalterHorbert = new wxStaticText(m_dScroll, wxID_ANY, wxT("Walter Horbert"), wxDefaultPosition, wxDefaultSize, 0); - m_dWalterHorbert->Wrap(-1); - m_dSizer->Add(m_dWalterHorbert, 0, wxALL, 5); - - m_dWilliamLD = new wxStaticText(m_dScroll, wxID_ANY, wxT("William Lloyd-Davies"), wxDefaultPosition, wxDefaultSize, 0); - m_dWilliamLD->Wrap(-1); - m_dSizer->Add(m_dWilliamLD, 0, wxALL, 5); - - m_dBratislavArandjelovic = new wxStaticText(m_dScroll, wxID_ANY, wxT("Bratislav Arandjelovic"), wxDefaultPosition, wxDefaultSize, 0); - m_dBratislavArandjelovic->Wrap(-1); - m_dSizer->Add(m_dBratislavArandjelovic, 0, wxALL, 5); - - m_dGaryMartin = new wxStaticText(m_dScroll, wxID_ANY, wxT("Gary Martin"), wxDefaultPosition, wxDefaultSize, 0); - m_dGaryMartin->Wrap(-1); - m_dSizer->Add(m_dGaryMartin, 0, wxALL, 5); - - m_dEinarsRepse = new wxStaticText(m_dScroll, wxID_ANY, wxT("Einars Repse"), wxDefaultPosition, wxDefaultSize, 0); - m_dEinarsRepse->Wrap(-1); - m_dSizer->Add(m_dEinarsRepse, 0, wxALL, 5); - - m_dTimothyGatton = new wxStaticText(m_dScroll, wxID_ANY, wxT("Timothy Gatton"), wxDefaultPosition, wxDefaultSize, 0); - m_dTimothyGatton->Wrap(-1); - m_dSizer->Add(m_dTimothyGatton, 0, wxALL, 5); - - m_dStephenCuccio = new wxStaticText(m_dScroll, wxID_ANY, wxT("Stephen Cuccio"), wxDefaultPosition, wxDefaultSize, 0); - m_dStephenCuccio->Wrap(-1); - m_dSizer->Add(m_dStephenCuccio, 0, wxALL, 5); - - m_dKeshavlalPatel = new wxStaticText(m_dScroll, wxID_ANY, wxT("Keshavlal Patel"), wxDefaultPosition, wxDefaultSize, 0); - m_dKeshavlalPatel->Wrap(-1); - m_dSizer->Add(m_dKeshavlalPatel, 0, wxALL, 5); - - m_dBobSchatzman = new wxStaticText(m_dScroll, wxID_ANY, wxT("Bob Schatzman"), wxDefaultPosition, wxDefaultSize, 0); - m_dBobSchatzman->Wrap(-1); - m_dSizer->Add(m_dBobSchatzman, 0, wxALL, 5); - - m_dRobertRoss = new wxStaticText(m_dScroll, wxID_ANY, wxT("Robert Ross"), wxDefaultPosition, wxDefaultSize, 0); - m_dRobertRoss->Wrap(-1); - m_dSizer->Add(m_dRobertRoss, 0, wxALL, 5); - - m_dRobertoBellotti = new wxStaticText(m_dScroll, wxID_ANY, wxT("Roberto Bellotti"), wxDefaultPosition, wxDefaultSize, 0); - m_dRobertoBellotti->Wrap(-1); - m_dSizer->Add(m_dRobertoBellotti, 0, wxALL, 5); - - m_dSergeVanderTorre = new wxStaticText(m_dScroll, wxID_ANY, wxT("Serge Van der Torre"), wxDefaultPosition, wxDefaultSize, 0); - m_dSergeVanderTorre->Wrap(-1); - m_dSizer->Add(m_dSergeVanderTorre, 0, wxALL, 5); - - m_dDieterSchneider = new wxStaticText(m_dScroll, wxID_ANY, wxT("Dieter Schneider"), wxDefaultPosition, wxDefaultSize, 0); - m_dDieterSchneider->Wrap(-1); - m_dSizer->Add(m_dDieterSchneider, 0, wxALL, 5); - - m_dPetrikaJaneku = new wxStaticText(m_dScroll, wxID_ANY, wxT("Petrika Janeku"), wxDefaultPosition, wxDefaultSize, 0); - m_dPetrikaJaneku->Wrap(-1); - m_dSizer->Add(m_dPetrikaJaneku, 0, wxALL, 5); - - - m_dBSizer->Add(m_dSizer, 1, wxALL | wxEXPAND, 5); - - - m_dScroll->SetSizer(m_dBSizer); + m_dSizer = new wxBoxSizer( wxVERTICAL ); + + m_dHeader = new wxStaticText( m_dScroll, wxID_ANY, wxT("Thanks to everyone who donated at cubicsdr.com!"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_dHeader->Wrap( -1 ); + m_dHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_dSizer->Add( m_dHeader, 0, wxALL, 5 ); + + m_dDivider1 = new wxStaticLine( m_dScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_dSizer->Add( m_dDivider1, 0, wxEXPAND | wxALL, 5 ); + + m_dSDRplay = new wxStaticText( m_dScroll, wxID_ANY, wxT("SDRplay / sdrplay.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dSDRplay->Wrap( -1 ); + m_dSizer->Add( m_dSDRplay, 0, wxALL, 5 ); + + m_dMichaelLadd = new wxStaticText( m_dScroll, wxID_ANY, wxT("Michael Ladd"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dMichaelLadd->Wrap( -1 ); + m_dSizer->Add( m_dMichaelLadd, 0, wxALL, 5 ); + + m_dAutoMotiveTemplates = new wxStaticText( m_dScroll, wxID_ANY, wxT("Automotive Templates"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dAutoMotiveTemplates->Wrap( -1 ); + m_dSizer->Add( m_dAutoMotiveTemplates, 0, wxALL, 5 ); + + m_dJorgeMorales = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jorge Morales"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJorgeMorales->Wrap( -1 ); + m_dSizer->Add( m_dJorgeMorales, 0, wxALL, 5 ); + + m_dMichaelRooke = new wxStaticText( m_dScroll, wxID_ANY, wxT("Michael Rooke"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dMichaelRooke->Wrap( -1 ); + m_dSizer->Add( m_dMichaelRooke, 0, wxALL, 5 ); + + m_dTNCOM = new wxStaticText( m_dScroll, wxID_ANY, wxT("TNCOM"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dTNCOM->Wrap( -1 ); + m_dSizer->Add( m_dTNCOM, 0, wxALL, 5 ); + + m_dErikWied = new wxStaticText( m_dScroll, wxID_ANY, wxT("Erik Mikkel Wied"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dErikWied->Wrap( -1 ); + m_dSizer->Add( m_dErikWied, 0, wxALL, 5 ); + + m_dRobertDuering = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Duering"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRobertDuering->Wrap( -1 ); + m_dSizer->Add( m_dRobertDuering, 0, wxALL, 5 ); + + m_dJimDeitch = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jim Deitch"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJimDeitch->Wrap( -1 ); + m_dSizer->Add( m_dJimDeitch, 0, wxALL, 5 ); + + m_dNooElec = new wxStaticText( m_dScroll, wxID_ANY, wxT("NooElec Inc. / nooelec.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dNooElec->Wrap( -1 ); + m_dSizer->Add( m_dNooElec, 0, wxALL, 5 ); + + m_dDavidAhlgren = new wxStaticText( m_dScroll, wxID_ANY, wxT("David Ahlgren"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dDavidAhlgren->Wrap( -1 ); + m_dSizer->Add( m_dDavidAhlgren, 0, wxALL, 5 ); + + m_dRonaldCook = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ronald Cook"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRonaldCook->Wrap( -1 ); + m_dSizer->Add( m_dRonaldCook, 0, wxALL, 5 ); + + m_dEricPeterson = new wxStaticText( m_dScroll, wxID_ANY, wxT("Eric Peterson"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dEricPeterson->Wrap( -1 ); + m_dSizer->Add( m_dEricPeterson, 0, wxALL, 5 ); + + m_dGeoDistributing = new wxStaticText( m_dScroll, wxID_ANY, wxT("Geo Distributing"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dGeoDistributing->Wrap( -1 ); + m_dSizer->Add( m_dGeoDistributing, 0, wxALL, 5 ); + + m_dJamesCarson = new wxStaticText( m_dScroll, wxID_ANY, wxT("James Carson"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJamesCarson->Wrap( -1 ); + m_dSizer->Add( m_dJamesCarson, 0, wxALL, 5 ); + + m_dCraigWilliams = new wxStaticText( m_dScroll, wxID_ANY, wxT("Craig Williams"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dCraigWilliams->Wrap( -1 ); + m_dSizer->Add( m_dCraigWilliams, 0, wxALL, 5 ); + + m_dRudolfShaffer = new wxStaticText( m_dScroll, wxID_ANY, wxT("Rudolf Schaffer"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRudolfShaffer->Wrap( -1 ); + m_dSizer->Add( m_dRudolfShaffer, 0, wxALL, 5 ); + + m_dJohnKaton = new wxStaticText( m_dScroll, wxID_ANY, wxT("John Katon"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJohnKaton->Wrap( -1 ); + m_dSizer->Add( m_dJohnKaton, 0, wxALL, 5 ); + + m_dVincentSonnier = new wxStaticText( m_dScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dVincentSonnier->Wrap( -1 ); + m_dSizer->Add( m_dVincentSonnier, 0, wxALL, 5 ); + + m_dCorq = new wxStaticText( m_dScroll, wxID_ANY, wxT("corq's auctions/L. Easterly LTD (x 4)"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dCorq->Wrap( -1 ); + m_dSizer->Add( m_dCorq, 0, wxALL, 5 ); + + m_dIvanAlekseev = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ivan Alekseev"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dIvanAlekseev->Wrap( -1 ); + m_dSizer->Add( m_dIvanAlekseev, 0, wxALL, 5 ); + + m_dOleJorgenKolsrud = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ole-Jørgen Næss Kolsrud"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dOleJorgenKolsrud->Wrap( -1 ); + m_dSizer->Add( m_dOleJorgenKolsrud, 0, wxALL, 5 ); + + m_dHenrikJagemyr = new wxStaticText( m_dScroll, wxID_ANY, wxT("Henrik Jagemyr"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dHenrikJagemyr->Wrap( -1 ); + m_dSizer->Add( m_dHenrikJagemyr, 0, wxALL, 5 ); + + m_dPeterHaines = new wxStaticText( m_dScroll, wxID_ANY, wxT("Peter Haines"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dPeterHaines->Wrap( -1 ); + m_dSizer->Add( m_dPeterHaines, 0, wxALL, 5 ); + + m_dLeonAbrassart = new wxStaticText( m_dScroll, wxID_ANY, wxT("Leon Abrassart"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dLeonAbrassart->Wrap( -1 ); + m_dSizer->Add( m_dLeonAbrassart, 0, wxALL, 5 ); + + m_dGeorgeTalbot = new wxStaticText( m_dScroll, wxID_ANY, wxT("George Alan Talbot"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dGeorgeTalbot->Wrap( -1 ); + m_dSizer->Add( m_dGeorgeTalbot, 0, wxALL, 5 ); + + m_dFranciscoPuerta = new wxStaticText( m_dScroll, wxID_ANY, wxT("Francisco Borja Marcos de la Puerta"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dFranciscoPuerta->Wrap( -1 ); + m_dSizer->Add( m_dFranciscoPuerta, 0, wxALL, 5 ); + + m_dRonaldLundeen = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ronald A. Lundeen"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRonaldLundeen->Wrap( -1 ); + m_dSizer->Add( m_dRonaldLundeen, 0, wxALL, 5 ); + + m_dWalterHorbert = new wxStaticText( m_dScroll, wxID_ANY, wxT("Walter Horbert"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dWalterHorbert->Wrap( -1 ); + m_dSizer->Add( m_dWalterHorbert, 0, wxALL, 5 ); + + m_dWilliamLD = new wxStaticText( m_dScroll, wxID_ANY, wxT("William Lloyd-Davies"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dWilliamLD->Wrap( -1 ); + m_dSizer->Add( m_dWilliamLD, 0, wxALL, 5 ); + + m_dBratislavArandjelovic = new wxStaticText( m_dScroll, wxID_ANY, wxT("Bratislav Arandjelovic"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dBratislavArandjelovic->Wrap( -1 ); + m_dSizer->Add( m_dBratislavArandjelovic, 0, wxALL, 5 ); + + m_dGaryMartin = new wxStaticText( m_dScroll, wxID_ANY, wxT("Gary Martin"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dGaryMartin->Wrap( -1 ); + m_dSizer->Add( m_dGaryMartin, 0, wxALL, 5 ); + + m_dEinarsRepse = new wxStaticText( m_dScroll, wxID_ANY, wxT("Einars Repse"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dEinarsRepse->Wrap( -1 ); + m_dSizer->Add( m_dEinarsRepse, 0, wxALL, 5 ); + + m_dTimothyGatton = new wxStaticText( m_dScroll, wxID_ANY, wxT("Timothy Gatton"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dTimothyGatton->Wrap( -1 ); + m_dSizer->Add( m_dTimothyGatton, 0, wxALL, 5 ); + + m_dStephenCuccio = new wxStaticText( m_dScroll, wxID_ANY, wxT("Stephen Cuccio"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dStephenCuccio->Wrap( -1 ); + m_dSizer->Add( m_dStephenCuccio, 0, wxALL, 5 ); + + m_dKeshavlalPatel = new wxStaticText( m_dScroll, wxID_ANY, wxT("Keshavlal Patel"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dKeshavlalPatel->Wrap( -1 ); + m_dSizer->Add( m_dKeshavlalPatel, 0, wxALL, 5 ); + + m_dBobSchatzman = new wxStaticText( m_dScroll, wxID_ANY, wxT("Bob Schatzman"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dBobSchatzman->Wrap( -1 ); + m_dSizer->Add( m_dBobSchatzman, 0, wxALL, 5 ); + + m_dRobertRoss = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Ross"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRobertRoss->Wrap( -1 ); + m_dSizer->Add( m_dRobertRoss, 0, wxALL, 5 ); + + m_dRobertoBellotti = new wxStaticText( m_dScroll, wxID_ANY, wxT("Roberto Bellotti"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRobertoBellotti->Wrap( -1 ); + m_dSizer->Add( m_dRobertoBellotti, 0, wxALL, 5 ); + + m_dSergeVanderTorre = new wxStaticText( m_dScroll, wxID_ANY, wxT("Serge Van der Torre"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dSergeVanderTorre->Wrap( -1 ); + m_dSizer->Add( m_dSergeVanderTorre, 0, wxALL, 5 ); + + m_dDieterSchneider = new wxStaticText( m_dScroll, wxID_ANY, wxT("Dieter Schneider"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dDieterSchneider->Wrap( -1 ); + m_dSizer->Add( m_dDieterSchneider, 0, wxALL, 5 ); + + m_dPetrikaJaneku = new wxStaticText( m_dScroll, wxID_ANY, wxT("Petrika Janeku"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dPetrikaJaneku->Wrap( -1 ); + m_dSizer->Add( m_dPetrikaJaneku, 0, wxALL, 5 ); + + m_dChadMyslinsky = new wxStaticText( m_dScroll, wxID_ANY, wxT("Chad Myslinsky"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dChadMyslinsky->Wrap( -1 ); + m_dSizer->Add( m_dChadMyslinsky, 0, wxALL, 5 ); + + m_dCharlieBruckner = new wxStaticText( m_dScroll, wxID_ANY, wxT("Charlie Bruckner"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dCharlieBruckner->Wrap( -1 ); + m_dSizer->Add( m_dCharlieBruckner, 0, wxALL, 5 ); + + m_dJordanParker = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jordan Parker"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dJordanParker->Wrap( -1 ); + m_dSizer->Add( m_dJordanParker, 0, wxALL, 5 ); + + m_dRobertChave = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Chave"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dRobertChave->Wrap( -1 ); + m_dSizer->Add( m_dRobertChave, 0, wxALL, 5 ); + + m_dMarvinCalvert = new wxStaticText( m_dScroll, wxID_ANY, wxT("Marvin Calvert"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dMarvinCalvert->Wrap( -1 ); + m_dSizer->Add( m_dMarvinCalvert, 0, wxALL, 5 ); + + + m_dBSizer->Add( m_dSizer, 1, wxALL|wxEXPAND, 5 ); + + + m_dScroll->SetSizer( m_dBSizer ); m_dScroll->Layout(); - m_dBSizer->Fit(m_dScroll); - m_aboutNotebook->AddPage(m_dScroll, wxT("Donations"), false); - m_stScroll = new wxScrolledWindow(m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL); - m_stScroll->SetScrollRate(5, 5); + m_dBSizer->Fit( m_dScroll ); + m_aboutNotebook->AddPage( m_dScroll, wxT("Donations"), false ); + m_stScroll = new wxScrolledWindow( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_stScroll->SetScrollRate( 5, 5 ); wxBoxSizer* m_stBSizer; - m_stBSizer = new wxBoxSizer(wxVERTICAL); - + m_stBSizer = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* m_stSizer; - m_stSizer = new wxBoxSizer(wxVERTICAL); - - m_stHeader = new wxStaticText(m_stScroll, wxID_ANY, wxT("Special Thanks To"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_stHeader->Wrap(-1); - m_stHeader->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString)); - - m_stSizer->Add(m_stHeader, 0, wxALL, 5); - - m_stDivider1 = new wxStaticLine(m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_stSizer->Add(m_stDivider1, 0, wxEXPAND | wxALL, 5); - - m_stSoapyDevAssistHeader = new wxStaticText(m_stScroll, wxID_ANY, wxT("SoapySDR Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_stSoapyDevAssistHeader->Wrap(-1); - m_stSoapyDevAssistHeader->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString)); - - m_stSizer->Add(m_stSoapyDevAssistHeader, 0, wxALL, 5); - - m_stJoshBlum = new wxStaticText(m_stScroll, wxID_ANY, wxT("Josh Blum / @guruofquality / pothosware.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stJoshBlum->Wrap(-1); - m_stSizer->Add(m_stJoshBlum, 0, wxALL, 5); - - m_stDivider2 = new wxStaticLine(m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_stSizer->Add(m_stDivider2, 0, wxEXPAND | wxALL, 5); - - m_stLiquidDSPHeader = new wxStaticText(m_stScroll, wxID_ANY, wxT("Liquid-DSP Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_stLiquidDSPHeader->Wrap(-1); - m_stLiquidDSPHeader->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString)); - - m_stSizer->Add(m_stLiquidDSPHeader, 0, wxALL, 5); - - m_stJosephGaeddert = new wxStaticText(m_stScroll, wxID_ANY, wxT("Joseph D. Gaeddert / @jgaeddert / liquidsdr.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stJosephGaeddert->Wrap(-1); - m_stSizer->Add(m_stJosephGaeddert, 0, wxALL, 5); - - m_stDivider3 = new wxStaticLine(m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - m_stSizer->Add(m_stDivider3, 0, wxEXPAND | wxALL, 5); - - m_stIdeasDirectionsHeader = new wxStaticText(m_stScroll, wxID_ANY, wxT("Ideas, Direction && Encouragement:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - m_stIdeasDirectionsHeader->Wrap(-1); - m_stIdeasDirectionsHeader->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString)); - - m_stSizer->Add(m_stIdeasDirectionsHeader, 0, wxALL, 5); - - m_stTonMachielsen = new wxStaticText(m_stScroll, wxID_ANY, wxT("Ton Machielsen / @Toontje / @EA3HOE "), wxDefaultPosition, wxDefaultSize, 0); - m_stTonMachielsen->Wrap(-1); - m_stSizer->Add(m_stTonMachielsen, 0, wxALL, 5); - - m_stMikeLadd = new wxStaticText(m_stScroll, wxID_ANY, wxT("Mike Ladd / KD2KOG.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stMikeLadd->Wrap(-1); - m_stSizer->Add(m_stMikeLadd, 0, wxALL, 5); - - m_stSDRplay = new wxStaticText(m_stScroll, wxID_ANY, wxT("SDRplay team / @SDRplay / SDRplay.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stSDRplay->Wrap(-1); - m_stSizer->Add(m_stSDRplay, 0, wxALL, 5); - - m_stSDRplayFB = new wxStaticText(m_stScroll, wxID_ANY, wxT("SDRplay Facebook group"), wxDefaultPosition, wxDefaultSize, 0); - m_stSDRplayFB->Wrap(-1); - m_stSizer->Add(m_stSDRplayFB, 0, wxALL, 5); - - m_stPaulWarren = new wxStaticText(m_stScroll, wxID_ANY, wxT("Paul Warren / @pwarren"), wxDefaultPosition, wxDefaultSize, 0); - m_stPaulWarren->Wrap(-1); - m_stSizer->Add(m_stPaulWarren, 0, wxALL, 5); - - m_stSegesdiKaroly = new wxStaticText(m_stScroll, wxID_ANY, wxT("Segesdi Károly / @jazzkutya"), wxDefaultPosition, wxDefaultSize, 0); - m_stSegesdiKaroly->Wrap(-1); - m_stSizer->Add(m_stSegesdiKaroly, 0, wxALL, 5); - - m_stRedditRTLSDR = new wxStaticText(m_stScroll, wxID_ANY, wxT("Reddit RTL-SDR group /r/rtlsdr"), wxDefaultPosition, wxDefaultSize, 0); - m_stRedditRTLSDR->Wrap(-1); - m_stSizer->Add(m_stRedditRTLSDR, 0, wxALL, 5); - - m_stNooElec = new wxStaticText(m_stScroll, wxID_ANY, wxT("NooElec team / NooElec.com"), wxDefaultPosition, wxDefaultSize, 0); - m_stNooElec->Wrap(-1); - m_stSizer->Add(m_stNooElec, 0, wxALL, 5); - - m_stGHIssues = new wxStaticText(m_stScroll, wxID_ANY, wxT("Everyone who's contributed to the GitHub issues; thanks!"), wxDefaultPosition, wxDefaultSize, 0); - m_stGHIssues->Wrap(-1); - m_stSizer->Add(m_stGHIssues, 0, wxALL, 5); - - m_stNominate = new wxStaticText(m_stScroll, wxID_ANY, wxT("Please feel free to nominate anyone we might have missed."), wxDefaultPosition, wxDefaultSize, 0); - m_stNominate->Wrap(-1); - m_stSizer->Add(m_stNominate, 0, wxALL, 5); - - - m_stBSizer->Add(m_stSizer, 1, wxALL | wxEXPAND, 5); - - - m_stScroll->SetSizer(m_stBSizer); + m_stSizer = new wxBoxSizer( wxVERTICAL ); + + m_stHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Special Thanks To"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_stHeader->Wrap( -1 ); + m_stHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + m_stSizer->Add( m_stHeader, 0, wxALL, 5 ); + + m_stDivider1 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_stSizer->Add( m_stDivider1, 0, wxEXPAND | wxALL, 5 ); + + m_stSoapyDevAssistHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("SoapySDR Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_stSoapyDevAssistHeader->Wrap( -1 ); + m_stSoapyDevAssistHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); + + m_stSizer->Add( m_stSoapyDevAssistHeader, 0, wxALL, 5 ); + + m_stJoshBlum = new wxStaticText( m_stScroll, wxID_ANY, wxT("Josh Blum / @guruofquality / pothosware.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stJoshBlum->Wrap( -1 ); + m_stSizer->Add( m_stJoshBlum, 0, wxALL, 5 ); + + m_stDivider2 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_stSizer->Add( m_stDivider2, 0, wxEXPAND | wxALL, 5 ); + + m_stLiquidDSPHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Liquid-DSP Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_stLiquidDSPHeader->Wrap( -1 ); + m_stLiquidDSPHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); + + m_stSizer->Add( m_stLiquidDSPHeader, 0, wxALL, 5 ); + + m_stJosephGaeddert = new wxStaticText( m_stScroll, wxID_ANY, wxT("Joseph D. Gaeddert / @jgaeddert / liquidsdr.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stJosephGaeddert->Wrap( -1 ); + m_stSizer->Add( m_stJosephGaeddert, 0, wxALL, 5 ); + + m_stDivider3 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + m_stSizer->Add( m_stDivider3, 0, wxEXPAND | wxALL, 5 ); + + m_stIdeasDirectionsHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Ideas, Direction && Encouragement:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); + m_stIdeasDirectionsHeader->Wrap( -1 ); + m_stIdeasDirectionsHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); + + m_stSizer->Add( m_stIdeasDirectionsHeader, 0, wxALL, 5 ); + + m_stTonMachielsen = new wxStaticText( m_stScroll, wxID_ANY, wxT("Ton Machielsen / @Toontje / @EA3HOE "), wxDefaultPosition, wxDefaultSize, 0 ); + m_stTonMachielsen->Wrap( -1 ); + m_stSizer->Add( m_stTonMachielsen, 0, wxALL, 5 ); + + m_stMikeLadd = new wxStaticText( m_stScroll, wxID_ANY, wxT("Mike Ladd / KD2KOG.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stMikeLadd->Wrap( -1 ); + m_stSizer->Add( m_stMikeLadd, 0, wxALL, 5 ); + + m_stSDRplay = new wxStaticText( m_stScroll, wxID_ANY, wxT("SDRplay team / @SDRplay / SDRplay.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stSDRplay->Wrap( -1 ); + m_stSizer->Add( m_stSDRplay, 0, wxALL, 5 ); + + m_stSDRplayFB = new wxStaticText( m_stScroll, wxID_ANY, wxT("SDRplay Facebook group"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stSDRplayFB->Wrap( -1 ); + m_stSizer->Add( m_stSDRplayFB, 0, wxALL, 5 ); + + m_stPaulWarren = new wxStaticText( m_stScroll, wxID_ANY, wxT("Paul Warren / @pwarren"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stPaulWarren->Wrap( -1 ); + m_stSizer->Add( m_stPaulWarren, 0, wxALL, 5 ); + + m_stSegesdiKaroly = new wxStaticText( m_stScroll, wxID_ANY, wxT("Segesdi Károly / @jazzkutya"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stSegesdiKaroly->Wrap( -1 ); + m_stSizer->Add( m_stSegesdiKaroly, 0, wxALL, 5 ); + + m_stRedditRTLSDR = new wxStaticText( m_stScroll, wxID_ANY, wxT("Reddit RTL-SDR group /r/rtlsdr"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stRedditRTLSDR->Wrap( -1 ); + m_stSizer->Add( m_stRedditRTLSDR, 0, wxALL, 5 ); + + m_stNooElec = new wxStaticText( m_stScroll, wxID_ANY, wxT("NooElec team / NooElec.com"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stNooElec->Wrap( -1 ); + m_stSizer->Add( m_stNooElec, 0, wxALL, 5 ); + + m_stGHIssues = new wxStaticText( m_stScroll, wxID_ANY, wxT("Everyone who's contributed to the GitHub issues; thanks!"), wxDefaultPosition, wxDefaultSize, 0 ); + m_stGHIssues->Wrap( -1 ); + m_stSizer->Add( m_stGHIssues, 0, wxALL, 5 ); + + m_stNominate = new wxStaticText( m_stScroll, wxID_ANY, wxT("Please feel free to nominate anyone we might have missed."), wxDefaultPosition, wxDefaultSize, 0 ); + m_stNominate->Wrap( -1 ); + m_stSizer->Add( m_stNominate, 0, wxALL, 5 ); + + + m_stBSizer->Add( m_stSizer, 1, wxALL|wxEXPAND, 5 ); + + + m_stScroll->SetSizer( m_stBSizer ); m_stScroll->Layout(); - m_stBSizer->Fit(m_stScroll); - m_aboutNotebook->AddPage(m_stScroll, wxT("Special Thanks"), false); - - dlgSizer->Add(m_aboutNotebook, 1, wxEXPAND | wxALL, 5); - - - this->SetSizer(dlgSizer); + m_stBSizer->Fit( m_stScroll ); + m_aboutNotebook->AddPage( m_stScroll, wxT("Special Thanks"), false ); + + dlgSizer->Add( m_aboutNotebook, 1, wxEXPAND | wxALL, 5 ); + + + this->SetSizer( dlgSizer ); this->Layout(); - - this->Centre(wxBOTH); + + this->Centre( wxBOTH ); } AboutDialogBase::~AboutDialogBase() diff --git a/src/forms/Dialog/AboutDialogBase.h b/src/forms/Dialog/AboutDialogBase.h index 099f910..a6b9cbd 100644 --- a/src/forms/Dialog/AboutDialogBase.h +++ b/src/forms/Dialog/AboutDialogBase.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 27 2017) +// C++ code generated with wxFormBuilder (version Nov 6 2017) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -32,114 +32,119 @@ /////////////////////////////////////////////////////////////////////////////// /// Class AboutDialogBase /////////////////////////////////////////////////////////////////////////////// -class AboutDialogBase : public wxDialog +class AboutDialogBase : public wxDialog { -private: - -protected: - wxPanel* m_hPanel; - wxStaticText* m_appName; - wxNotebook* m_aboutNotebook; - wxScrolledWindow* m_dbScroll; - wxStaticText* m_dbHeader; - wxStaticText* m_dbGHHeader; - wxStaticText* m_dbTwitter; - wxStaticText* m_dbCharlesCliffe; - wxStaticText* m_dbghCC; - wxStaticText* m_dbtCC; - wxStaticText* m_dbVincentSonnier; - wxStaticText* m_dbghVS; - wxStaticText* m_dbtVS; - wxStaticLine* m_dbDivider1; - wxStaticText* m_cContributorsHeader; - wxStaticText* m_cGitHub; - wxStaticText* m_cCorneLukken; - wxStaticText* m_cghCL; - wxStaticText* m_cStainislawPitucha; - wxStaticText* m_cghSP; - wxStaticText* m_cghStefanTalpalaru; - wxStaticText* m_cghST; - wxStaticText* m_cCrisMotch; - wxStaticText* m_cghCM; - wxStaticText* m_cMariuszRyndzionek; - wxStaticText* m_cghMR; - wxStaticText* m_cJiangWei; - wxStaticText* m_cghJW; - wxStaticText* m_cTomSwartz; - wxStaticText* m_cghTS; - wxStaticText* m_cInfinityCyberworks; - wxStaticText* m_cghIC; - wxScrolledWindow* m_dScroll; - wxStaticText* m_dHeader; - wxStaticLine* m_dDivider1; - wxStaticText* m_dSDRplay; - wxStaticText* m_dMichaelLadd; - wxStaticText* m_dAutoMotiveTemplates; - wxStaticText* m_dJorgeMorales; - wxStaticText* m_dMichaelRooke; - wxStaticText* m_dTNCOM; - wxStaticText* m_dErikWied; - wxStaticText* m_dRobertDuering; - wxStaticText* m_dJimDeitch; - wxStaticText* m_dNooElec; - wxStaticText* m_dDavidAhlgren; - wxStaticText* m_dRonaldCook; - wxStaticText* m_dEricPeterson; - wxStaticText* m_dGeoDistributing; - wxStaticText* m_dJamesCarson; - wxStaticText* m_dCraigWilliams; - wxStaticText* m_dRudolfShaffer; - wxStaticText* m_dJohnKaton; - wxStaticText* m_dVincentSonnier; - wxStaticText* m_dCorq; - wxStaticText* m_dIvanAlekseev; - wxStaticText* m_dOleJorgenKolsrud; - wxStaticText* m_dHenrikJagemyr; - wxStaticText* m_dPeterHaines; - wxStaticText* m_dLeonAbrassart; - wxStaticText* m_dGeorgeTalbot; - wxStaticText* m_dFranciscoPuerta; - wxStaticText* m_dRonaldLundeen; - wxStaticText* m_dWalterHorbert; - wxStaticText* m_dWilliamLD; - wxStaticText* m_dBratislavArandjelovic; - wxStaticText* m_dGaryMartin; - wxStaticText* m_dEinarsRepse; - wxStaticText* m_dTimothyGatton; - wxStaticText* m_dStephenCuccio; - wxStaticText* m_dKeshavlalPatel; - wxStaticText* m_dBobSchatzman; - wxStaticText* m_dRobertRoss; - wxStaticText* m_dRobertoBellotti; - wxStaticText* m_dSergeVanderTorre; - wxStaticText* m_dDieterSchneider; - wxStaticText* m_dPetrikaJaneku; - wxScrolledWindow* m_stScroll; - wxStaticText* m_stHeader; - wxStaticLine* m_stDivider1; - wxStaticText* m_stSoapyDevAssistHeader; - wxStaticText* m_stJoshBlum; - wxStaticLine* m_stDivider2; - wxStaticText* m_stLiquidDSPHeader; - wxStaticText* m_stJosephGaeddert; - wxStaticLine* m_stDivider3; - wxStaticText* m_stIdeasDirectionsHeader; - wxStaticText* m_stTonMachielsen; - wxStaticText* m_stMikeLadd; - wxStaticText* m_stSDRplay; - wxStaticText* m_stSDRplayFB; - wxStaticText* m_stPaulWarren; - wxStaticText* m_stSegesdiKaroly; - wxStaticText* m_stRedditRTLSDR; - wxStaticText* m_stNooElec; - wxStaticText* m_stGHIssues; - wxStaticText* m_stNominate; - -public: - - AboutDialogBase(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("About"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(530, 420), long style = wxDEFAULT_DIALOG_STYLE); - ~AboutDialogBase(); - + private: + + protected: + wxPanel* m_hPanel; + wxStaticText* m_appName; + wxNotebook* m_aboutNotebook; + wxScrolledWindow* m_dbScroll; + wxStaticText* m_dbHeader; + wxStaticText* m_dbGHHeader; + wxStaticText* m_dbTwitter; + wxStaticText* m_dbCharlesCliffe; + wxStaticText* m_dbghCC; + wxStaticText* m_dbtCC; + wxStaticText* m_dbVincentSonnier; + wxStaticText* m_dbghVS; + wxStaticText* m_dbtVS; + wxStaticLine* m_dbDivider1; + wxStaticText* m_cContributorsHeader; + wxStaticText* m_cGitHub; + wxStaticText* m_cCorneLukken; + wxStaticText* m_cghCL; + wxStaticText* m_cStainislawPitucha; + wxStaticText* m_cghSP; + wxStaticText* m_cghStefanTalpalaru; + wxStaticText* m_cghST; + wxStaticText* m_cCrisMotch; + wxStaticText* m_cghCM; + wxStaticText* m_cMariuszRyndzionek; + wxStaticText* m_cghMR; + wxStaticText* m_cJiangWei; + wxStaticText* m_cghJW; + wxStaticText* m_cTomSwartz; + wxStaticText* m_cghTS; + wxStaticText* m_cInfinityCyberworks; + wxStaticText* m_cghIC; + wxScrolledWindow* m_dScroll; + wxStaticText* m_dHeader; + wxStaticLine* m_dDivider1; + wxStaticText* m_dSDRplay; + wxStaticText* m_dMichaelLadd; + wxStaticText* m_dAutoMotiveTemplates; + wxStaticText* m_dJorgeMorales; + wxStaticText* m_dMichaelRooke; + wxStaticText* m_dTNCOM; + wxStaticText* m_dErikWied; + wxStaticText* m_dRobertDuering; + wxStaticText* m_dJimDeitch; + wxStaticText* m_dNooElec; + wxStaticText* m_dDavidAhlgren; + wxStaticText* m_dRonaldCook; + wxStaticText* m_dEricPeterson; + wxStaticText* m_dGeoDistributing; + wxStaticText* m_dJamesCarson; + wxStaticText* m_dCraigWilliams; + wxStaticText* m_dRudolfShaffer; + wxStaticText* m_dJohnKaton; + wxStaticText* m_dVincentSonnier; + wxStaticText* m_dCorq; + wxStaticText* m_dIvanAlekseev; + wxStaticText* m_dOleJorgenKolsrud; + wxStaticText* m_dHenrikJagemyr; + wxStaticText* m_dPeterHaines; + wxStaticText* m_dLeonAbrassart; + wxStaticText* m_dGeorgeTalbot; + wxStaticText* m_dFranciscoPuerta; + wxStaticText* m_dRonaldLundeen; + wxStaticText* m_dWalterHorbert; + wxStaticText* m_dWilliamLD; + wxStaticText* m_dBratislavArandjelovic; + wxStaticText* m_dGaryMartin; + wxStaticText* m_dEinarsRepse; + wxStaticText* m_dTimothyGatton; + wxStaticText* m_dStephenCuccio; + wxStaticText* m_dKeshavlalPatel; + wxStaticText* m_dBobSchatzman; + wxStaticText* m_dRobertRoss; + wxStaticText* m_dRobertoBellotti; + wxStaticText* m_dSergeVanderTorre; + wxStaticText* m_dDieterSchneider; + wxStaticText* m_dPetrikaJaneku; + wxStaticText* m_dChadMyslinsky; + wxStaticText* m_dCharlieBruckner; + wxStaticText* m_dJordanParker; + wxStaticText* m_dRobertChave; + wxStaticText* m_dMarvinCalvert; + wxScrolledWindow* m_stScroll; + wxStaticText* m_stHeader; + wxStaticLine* m_stDivider1; + wxStaticText* m_stSoapyDevAssistHeader; + wxStaticText* m_stJoshBlum; + wxStaticLine* m_stDivider2; + wxStaticText* m_stLiquidDSPHeader; + wxStaticText* m_stJosephGaeddert; + wxStaticLine* m_stDivider3; + wxStaticText* m_stIdeasDirectionsHeader; + wxStaticText* m_stTonMachielsen; + wxStaticText* m_stMikeLadd; + wxStaticText* m_stSDRplay; + wxStaticText* m_stSDRplayFB; + wxStaticText* m_stPaulWarren; + wxStaticText* m_stSegesdiKaroly; + wxStaticText* m_stRedditRTLSDR; + wxStaticText* m_stNooElec; + wxStaticText* m_stGHIssues; + wxStaticText* m_stNominate; + + public: + + AboutDialogBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("About"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 530,420 ), long style = wxDEFAULT_DIALOG_STYLE ); + ~AboutDialogBase(); + }; #endif //__ABOUTDIALOGBASE_H__ diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index 5211d5e..cd56408 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -104,17 +104,26 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstancePtr demod, RGBA4f color, glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); bool soloMode = wxGetApp().getSoloMode(); + bool isRecording = demod->isRecording(); bool isSolo = soloMode && demod == wxGetApp().getDemodMgr().getLastActiveDemodulator(); + RGBA4f labelBg(0, 0, 0, 0.35f); + if (isSolo) { - glColor4f(0.8f, 0.8f, 0, 0.35f); + labelBg.r = labelBg.g = 0.8f; } else if (demod->isMuted()) { - glColor4f(0.8f, 0, 0, 0.35f); + labelBg.r = 0.8f; } else if (soloMode) { - glColor4f(0.2f, 0, 0, 0.35f); - } else { - glColor4f(0, 0, 0, 0.35f); + labelBg.r = 0.2f; } + + // TODO: Better recording indicator... pulsating red circle? + if (isRecording) { + auto t = std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + labelBg.g = sinf(2.0f * M_PI * (float(t) / 1000.0f)) * 0.25f + 0.75f; + } + + glColor4f(labelBg.r, labelBg.g, labelBg.b, labelBg.a); glBegin(GL_QUADS); glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0); @@ -156,18 +165,29 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstancePtr demod, RGBA4f color, glColor4f(1.0, 1.0, 1.0, 0.8f); - std::string demodLabel = demod->getLabel(); - - if (demod->isMuted()) { - demodLabel = std::string("[M] ") + demodLabel; - } else if (isSolo) { - demodLabel = std::string("[S] ") + demodLabel; - } - + std::string demodLabel, demodPrefix; + if (demod->isDeltaLock()) { - demodLabel.append(" [V]"); + demodPrefix.append("V"); } - + + if (isRecording) { + demodPrefix.append("R"); + } + + if (demod->isMuted()) { + demodPrefix.append("M"); + } else if (isSolo) { + demodPrefix.append("S"); + } + + // Set the prefix + if (!demodPrefix.empty()) { + demodLabel = "[" + demodPrefix + "] "; + } + // Append the default label + demodLabel.append(demod->getLabel()); + if (demod->getDemodulatorType() == "USB") { GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true); } else if (demod->getDemodulatorType() == "LSB") { diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index a951a1f..8efd1e5 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -565,25 +565,25 @@ 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, E to edit label."); + setStatusText("Drag to change bandwidth. SPACE or 0-9 for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record."); } 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, E to edit label."); + setStatusText("Drag to change frequency; SPACE or 0-9 for direct input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record."); } } else { SetCursor(wxCURSOR_CROSS); nextDragState = WF_DRAG_NONE; if (shiftDown) { - setStatusText("Click to create a new demodulator or hold ALT to drag range, SPACE or numeric key for direct center frequency input."); + setStatusText("Click to create a new demodulator or hold ALT to drag new range."); } else { setStatusText( - "Click to set active demodulator frequency or hold ALT to drag range; hold SHIFT to create new. Right drag or wheel to Zoom. Arrow keys to navigate/zoom, C to center."); + "Click to set demodulator frequency or hold ALT to drag range; hold SHIFT to create new. Right drag or wheel to Zoom. Arrow keys to navigate/zoom, C to center. Shift-R record/stop all."); } } }