diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml index 3d70d25f..72b78ec9 100644 --- a/.github/workflows/cmake-linux.yml +++ b/.github/workflows/cmake-linux.yml @@ -14,6 +14,10 @@ env: BUILD_TYPE: RelWithDebInfo TORCH_RELEASE: 2.9.1 + # Needed to suppress possible cause of test errors. + # See https://github.com/alexmurray/emacs-snap/issues/60 + NO_AT_BRIDGE: 1 + jobs: lint: runs-on: ubuntu-24.04 diff --git a/.github/workflows/cmake-windows.yml b/.github/workflows/cmake-windows.yml index 8e89d607..5881a89f 100644 --- a/.github/workflows/cmake-windows.yml +++ b/.github/workflows/cmake-windows.yml @@ -134,7 +134,7 @@ jobs: - name: Install VB-Cable ("Radio" sound device) uses: LABSN/sound-ci-helpers@v1 - - run: 'Invoke-WebRequest https://software.muzychenko.net/trials/vac464.zip -OutFile vac464.zip' + - run: 'Invoke-WebRequest https://k6aq.net/freedv-build/vac464.zip -OutFile vac464.zip' - run: 'Expand-Archive -Path vac464.zip -DestinationPath vac464' - run: 'Import-Certificate -FilePath ${{github.workspace}}\test\vac464.cer -CertStoreLocation Cert:\LocalMachine\root' - run: 'Import-Certificate -FilePath ${{github.workspace}}\test\vac464.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisher' diff --git a/USER_MANUAL.md b/USER_MANUAL.md index 50775cd8..a77cdecb 100644 --- a/USER_MANUAL.md +++ b/USER_MANUAL.md @@ -870,6 +870,8 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes * Flex: Report FreeDV SNR using SmartSDR Meter API. (PR #1119) * Add support for BBWENet bandwidth expander for received RADE audio. (PR #1113) * Reduce CPU usage rendering "scalar" plots (i.e. From Mic). (PR #1133) + * FreeDV Reporter: Combine Version and Status columns to save horizontal space. (PR #1132) + * FreeDV Reporter: Allow columns to be rearranged and/or made invisible. (PR #1132) *Note: Legacy modes (700D, 700E, 1600) are now hidden by default. (PR #1108) You can show them again by going to Tools->Options->Modem and selecting "Enable Legacy Modes".* @@ -887,6 +889,7 @@ again by going to Tools->Options->Modem and selecting "Enable Legacy Modes".* * OmniRig: Fix crash when using Test button in CAT config dialog. (PR #1126) * Fix hidden/clipped axis labels on plots. (PR #1110) * Work around deadlock bug in tty0tty. (PR #1134) - thanks @barjac! + * Zero out waterfall when transmitting and not in full duplex. (PR #1136) 2. Enhancements: * FreeDV Reporter: Sort empty user messages below non-empty ones. (PR #1105) * Linux: List /dev/rfcomm* serial devices when configuring. (PR #1106) - thanks @NespaLa! diff --git a/src/config/ReportingConfiguration.cpp b/src/config/ReportingConfiguration.cpp index 67f715e6..21939208 100644 --- a/src/config/ReportingConfiguration.cpp +++ b/src/config/ReportingConfiguration.cpp @@ -50,13 +50,16 @@ ReportingConfiguration::ReportingConfiguration() , freedvReporterBandFilterTracksExactFreq("/Reporting/FreeDV/BandFilterTracking/TracksExactFreq", false) , freedvReporterStatusText("/Reporting/FreeDV/StatusText", _("")) , freedvReporterRecentStatusTexts("/Reporting/FreeDV/RecentStatusTexts", {}) - + + , freedvReporterColumnOrder("/Reporting/FreeDV/ColumnOrder", { }) /* empty means default ordering */ + , freedvReporterColumnVisibility("/Reporting/FreeDV/ColumnVisibility", { }) + , udpReportingEnabled("/Reporting/UDP/Enable", false) , udpReportingHostname("/Reporting/UDP/Hostname", _("127.0.0.1")) , udpReportingPort("/Reporting/UDP/Port", 2237) , useUTCForReporting("/CallsignList/UseUTCTime", false) - + , reportingFrequencyList("/Reporting/FrequencyList", { _("1.8700"), _("3.6250"), @@ -190,6 +193,9 @@ void ReportingConfiguration::load(wxConfigBase* config) load_(config, udpReportingHostname); load_(config, udpReportingPort); + load_(config, freedvReporterColumnOrder); + load_(config, freedvReporterColumnVisibility); + load_(config, useUTCForReporting); // Note: this needs to be loaded before the frequency list so that @@ -235,6 +241,9 @@ void ReportingConfiguration::save(wxConfigBase* config) save_(config, freedvReporterStatusText); save_(config, freedvReporterRecentStatusTexts); + save_(config, freedvReporterColumnOrder); + save_(config, freedvReporterColumnVisibility); + save_(config, udpReportingEnabled); save_(config, udpReportingHostname); save_(config, udpReportingPort); diff --git a/src/config/ReportingConfiguration.h b/src/config/ReportingConfiguration.h index 7c566707..c05cb568 100644 --- a/src/config/ReportingConfiguration.h +++ b/src/config/ReportingConfiguration.h @@ -57,6 +57,9 @@ public: ConfigurationDataElement freedvReporterStatusText; ConfigurationDataElement > freedvReporterRecentStatusTexts; + ConfigurationDataElement > freedvReporterColumnOrder; + ConfigurationDataElement > freedvReporterColumnVisibility; + ConfigurationDataElement udpReportingEnabled; ConfigurationDataElement udpReportingHostname; ConfigurationDataElement udpReportingPort; diff --git a/src/config/WxWidgetsConfigStore.cpp b/src/config/WxWidgetsConfigStore.cpp index 68c31727..cf7a31c2 100644 --- a/src/config/WxWidgetsConfigStore.cpp +++ b/src/config/WxWidgetsConfigStore.cpp @@ -20,6 +20,7 @@ //========================================================================== #include +#include #include "WxWidgetsConfigStore.h" template<> @@ -30,6 +31,40 @@ void WxWidgetsConfigStore::load_(wxConfigBase* config, Configurati configElement.setWithoutProcessing((unsigned int)val); } +template<> +void WxWidgetsConfigStore::load_ >(wxConfigBase* config, ConfigurationDataElement >& configElement) +{ + wxString val; + wxString defaultVal = generateStringFromArray_(configElement.getDefaultVal()); + + config->Read(configElement.getElementName(), &val, defaultVal); + configElement.setWithoutProcessing(generateNumArrayFromString_(val)); +} + +template<> +void WxWidgetsConfigStore::save_ >(wxConfigBase* config, ConfigurationDataElement >& configElement) +{ + wxString val = generateStringFromArray_(configElement.getWithoutProcessing()); + config->Write(configElement.getElementName(), val); +} + +template<> +void WxWidgetsConfigStore::load_ >(wxConfigBase* config, ConfigurationDataElement >& configElement) +{ + wxString val; + wxString defaultVal = generateStringFromArray_(configElement.getDefaultVal()); + + config->Read(configElement.getElementName(), &val, defaultVal); + configElement.setWithoutProcessing(generateBoolArrayFromString_(val)); +} + +template<> +void WxWidgetsConfigStore::save_ >(wxConfigBase* config, ConfigurationDataElement >& configElement) +{ + wxString val = generateStringFromArray_(configElement.getWithoutProcessing()); + config->Write(configElement.getElementName(), val); +} + /* Note: for string arrays, we're treating them as a list of strings separated by commas. */ template<> @@ -39,7 +74,7 @@ void WxWidgetsConfigStore::load_ >(wxConfigBase* config, C wxString defaultVal = generateStringFromArray_(configElement.getDefaultVal()); config->Read(configElement.getElementName(), &val, defaultVal); - configElement.setWithoutProcessing(generateArayFromString_(val)); + configElement.setWithoutProcessing(generateStrArrayFromString_(val)); } template<> @@ -49,6 +84,67 @@ void WxWidgetsConfigStore::save_ >(wxConfigBase* config, C config->Write(configElement.getElementName(), val); } +wxString WxWidgetsConfigStore::generateStringFromArray_(std::vector const& vec) +{ + wxString rv = ""; + + int count = vec.size(); + for (auto& item : vec) + { + wxString numAsString = wxNumberFormatter::ToString((long)item, wxNumberFormatter::Style_None); + rv += numAsString; + count--; + + if (count > 0) + { + rv += ","; + } + } + + return rv; +} + +wxString WxWidgetsConfigStore::generateStringFromArray_(std::vector const& vec) +{ + std::vector tmpVec; + tmpVec.reserve(vec.size()); + for (auto v : vec) + { + tmpVec.push_back(v ? 1 : 0); + } + return generateStringFromArray_(tmpVec); +} + +std::vector WxWidgetsConfigStore::generateBoolArrayFromString_(wxString const& str) +{ + std::vector tmpVec = generateNumArrayFromString_(str); + std::vector rv; + + rv.reserve(tmpVec.size()); + for (auto& v : tmpVec) + { + rv.push_back(v == 1 ? true : false); + } + + return rv; +} + +std::vector WxWidgetsConfigStore::generateNumArrayFromString_(wxString const& str) +{ + std::vector rv; + + wxStringTokenizer tokenizer(str, ","); + while ( tokenizer.HasMoreTokens() ) + { + wxString token = tokenizer.GetNextToken(); + long tmp = 0; + wxNumberFormatter::FromString(token, &tmp); + rv.push_back((int)tmp); + } + + return rv; +} + wxString WxWidgetsConfigStore::generateStringFromArray_(std::vector const& vec) { wxString rv = ""; @@ -68,7 +164,7 @@ wxString WxWidgetsConfigStore::generateStringFromArray_(std::vector co return rv; } -std::vector WxWidgetsConfigStore::generateArayFromString_(wxString const& str) +std::vector WxWidgetsConfigStore::generateStrArrayFromString_(wxString const& str) { std::vector rv; diff --git a/src/config/WxWidgetsConfigStore.h b/src/config/WxWidgetsConfigStore.h index 9add7cb0..8cc02194 100644 --- a/src/config/WxWidgetsConfigStore.h +++ b/src/config/WxWidgetsConfigStore.h @@ -42,7 +42,13 @@ protected: void save_(wxConfigBase* config, ConfigurationDataElement& configElement); wxString generateStringFromArray_(std::vector const& vec); - std::vector generateArayFromString_(wxString const& str); + std::vector generateStrArrayFromString_(wxString const& str); + + wxString generateStringFromArray_(std::vector const& vec); + std::vector generateNumArrayFromString_(wxString const& str); + + wxString generateStringFromArray_(std::vector const& vec); + std::vector generateBoolArrayFromString_(wxString const& str); }; template @@ -68,4 +74,16 @@ void WxWidgetsConfigStore::load_ >(wxConfigBase* config, C template<> void WxWidgetsConfigStore::save_ >(wxConfigBase* config, ConfigurationDataElement >& configElement); +// Special handling for loading and saving int arrays. +template<> +void WxWidgetsConfigStore::load_ >(wxConfigBase* config, ConfigurationDataElement >& configElement); +template<> +void WxWidgetsConfigStore::save_ >(wxConfigBase* config, ConfigurationDataElement >& configElement); + +// Special handling for loading and saving bool arrays. +template<> +void WxWidgetsConfigStore::load_ >(wxConfigBase* config, ConfigurationDataElement >& configElement); +template<> +void WxWidgetsConfigStore::save_ >(wxConfigBase* config, ConfigurationDataElement >& configElement); + #endif // WXWIDGETS_CONFIG_STORE_H diff --git a/src/gui/dialogs/freedv_reporter.cpp b/src/gui/dialogs/freedv_reporter.cpp index c089755d..fd74a53b 100644 --- a/src/gui/dialogs/freedv_reporter.cpp +++ b/src/gui/dialogs/freedv_reporter.cpp @@ -19,6 +19,7 @@ // //========================================================================== +#include #include #include #include @@ -29,6 +30,10 @@ #include #endif // wxCHECK_VERSION(3,2,0) +#if defined(WIN32) +#include +#endif // defined(WIN32) + #include "freedv_reporter.h" #include "freedv_interface.h" @@ -62,6 +67,115 @@ extern FreeDVInterface freedvInterface; using namespace std::placeholders; +void FreeDVReporterDialog::createColumn_(int col, bool visible) +{ + wxDataViewColumn* colObj = nullptr; + int minWidth = 0; + int alignment = wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL; + wxString colName = ""; + bool ellipsize = false; + + switch (col) + { + case CALLSIGN_COL: + colName = wxT("Callsign"); + minWidth = 70; + break; + case GRID_SQUARE_COL: + colName = wxT("Locator"); + minWidth = 65; + break; + case DISTANCE_COL: + colName = wxT("km"); + minWidth = 60; + alignment = wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL; + break; + case HEADING_COL: + colName = wxT("Hdg"); + minWidth = 60; + alignment = wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL; + break; + case VERSION_COL: + colName = wxT("Version"); + minWidth = 70; + break; + case FREQUENCY_COL: + colName = wxGetApp().appConfiguration.reportingConfiguration.reportingFrequencyAsKhz ? wxT("kHz") : wxT("MHz"); + minWidth = 60; + alignment = wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL; + break; + case TX_MODE_COL: + colName = wxT("Mode"); + minWidth = 65; + break; + case STATUS_COL: + colName = wxT("Status"); + minWidth = 60; + break; + case USER_MESSAGE_COL: + // Note: there's Windows specific logic here, so we create the column + // here rather than farther down. +#if defined(WIN32) + // Use ReportMessageRenderer only on Windows so that we can render emojis in color. + colObj = new wxDataViewColumn(wxT("Msg"), new ReportMessageRenderer(), col, wxCOL_WIDTH_DEFAULT, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE); + m_listSpots->AppendColumn(colObj); +#else + colObj = m_listSpots->AppendTextColumn(wxT("Msg"), col, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_DEFAULT, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE); +#endif // defined(WIN32) + colObj->SetWidth(wxGetApp().appConfiguration.reportingUserMsgColWidth); + minWidth = 130; + ellipsize = true; + break; + case LAST_TX_DATE_COL: + colName = wxT("Last TX"); + minWidth = 60; + break; + case LAST_RX_CALLSIGN_COL: + colName = wxT("RX Call"); + minWidth = 65; + break; + case LAST_RX_MODE_COL: + colName = wxT("Mode"); + minWidth = 60; + break; + case SNR_COL: + colName = wxT("SNR"); + minWidth = 60; + break; + case LAST_UPDATE_DATE_COL: + colName = wxT("Last Update"); + minWidth = 100; + break; + default: + return; + } + + if (colObj == nullptr) + { + colObj = m_listSpots->AppendTextColumn(colName, col, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE); + } + auto renderer = colObj->GetRenderer(); + renderer->SetAlignment(alignment); + if (ellipsize) + { + renderer->EnableEllipsize(wxELLIPSIZE_END); + } + else + { + renderer->DisableEllipsize(); + } + colObj->SetMinWidth(minWidth); + if (col == wxGetApp().appConfiguration.reporterWindowCurrentSort) + { + colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); + } + + if (!visible) + { + colObj->SetHidden(true); + } +} + FreeDVReporterDialog::FreeDVReporterDialog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxFrame(parent, id, title, pos, size, style) , tipWindow_(nullptr) @@ -90,151 +204,87 @@ FreeDVReporterDialog::FreeDVReporterDialog(wxWindow* parent, wxWindowID id, cons // Main list box // ============================= - int col = 0; - m_listSpots = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_SINGLE); // Associate data model. spotsDataModel_ = new FreeDVReporterDataModel(this); m_listSpots->AssociateModel(spotsDataModel_.get()); - auto colObj = m_listSpots->AppendTextColumn(wxT("Callsign"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->GetRenderer()->DisableEllipsize(); - colObj->SetMinWidth(70); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) + if (wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->size() != NUM_COLS) { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); + // Generate default column ordering + log_info("Generating missing column ordering"); + auto iter = wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->begin(); + while (iter != wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->end()) + { + if (*iter >= RIGHTMOST_COL) + { + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->erase(iter); + iter = wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->begin(); + } + else + { + iter++; + } + } + + auto maxIndex = + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->size() == 0 ? + -1 : + *std::max_element( + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->begin(), + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->end() + ); + + for (auto index = maxIndex + 1; index < NUM_COLS; index++) + { + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder->push_back(index); + } } - - colObj = m_listSpots->AppendTextColumn(wxT("Locator"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(65); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) + + while (wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnVisibility->size() < NUM_COLS) { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); + // Generate default column visibility + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnVisibility->push_back(true); } - - colObj = m_listSpots->AppendTextColumn(wxT("km"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(60); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn(wxT("Hdg"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(60); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn(wxT("Version"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(70); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn( - wxGetApp().appConfiguration.reportingConfiguration.reportingFrequencyAsKhz ? wxT("kHz") : wxT("MHz"), - col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(60); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn(wxT("Mode"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(65); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn(wxT("Status"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(60); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - + + // Windows seems to have the model column ID equal to the actual column ID regardless of the + // actual ordering, so we just use wxHeaderCtrl to save/restore the column ordering. #if defined(WIN32) - // Use ReportMessageRenderer only on Windows so that we can render emojis in color. - colObj = new wxDataViewColumn(wxT("Msg"), new ReportMessageRenderer(), col++, wxCOL_WIDTH_DEFAULT, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - m_listSpots->AppendColumn(colObj); -#else - colObj = m_listSpots->AppendTextColumn(wxT("Msg"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_DEFAULT, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); + for (auto col = 0; col < NUM_COLS; col++) +#else + for (auto& col : wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder.get()) #endif // defined(WIN32) - colObj->GetRenderer()->EnableEllipsize(wxELLIPSIZE_END); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(130); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - colObj->SetWidth(wxGetApp().appConfiguration.reportingUserMsgColWidth); - - colObj = m_listSpots->AppendTextColumn(wxT("Last TX"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(60); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn(wxT("RX Call"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(65); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn(wxT("Mode"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(60); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn(wxT("SNR"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(60); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } - - colObj = m_listSpots->AppendTextColumn(wxT("Last Update"), col++, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - colObj->GetRenderer()->DisableEllipsize(); - colObj->GetRenderer()->SetAlignment(wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL); - colObj->SetMinWidth(100); - if ((col - 1) == wxGetApp().appConfiguration.reporterWindowCurrentSort) - { - colObj->SetSortOrder(wxGetApp().appConfiguration.reporterWindowCurrentSortDirection); - } + if (col < NUM_COLS) + { + log_info("Creating col %d", col); + auto visible = (bool)wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnVisibility->at(col); - m_listSpots->AppendTextColumn(wxT(" "), col++, wxDATAVIEW_CELL_INERT, 1, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE); + // Hide RX Mode column if legacy modes aren't enabled + if (col == LAST_RX_MODE_COL) + { + visible &= wxGetApp().appConfiguration.enableLegacyModes; + } + createColumn_(col, visible); + } + } + m_listSpots->AppendTextColumn(wxT(" "), RIGHTMOST_COL, wxDATAVIEW_CELL_INERT, 1, wxALIGN_CENTER, wxDATAVIEW_COL_RESIZABLE); +#if defined(WIN32) + auto headerCtrl = m_listSpots->GenericGetHeader(); + wxArrayInt wxColumnOrder; + for (auto& col : wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder.get()) + { + if (col < NUM_COLS) + { + wxColumnOrder.Add(col); + } + } + wxColumnOrder.Add(RIGHTMOST_COL); // All columns need to be in the list to actually take effect. + headerCtrl->SetColumnsOrder(wxColumnOrder); +#endif // defined(WIN32) + sectionSizer->Add(m_listSpots, 0, wxALL | wxEXPAND, 2); // Bottom buttons @@ -289,6 +339,7 @@ FreeDVReporterDialog::FreeDVReporterDialog(wxWindow* parent, wxWindowID id, cons bandFilterSizer->Add(m_bandFilter, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); m_trackFrequency = new wxCheckBox(this, wxID_ANY, _("Track:"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + m_trackFrequency->SetToolTip(_("Filters FreeDV Reporter list based on radio's current frequency or band.")); bandFilterSizer->Add(m_trackFrequency, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5); m_trackFreqBand = new wxRadioButton(this, wxID_ANY, _("band"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); @@ -322,6 +373,42 @@ FreeDVReporterDialog::FreeDVReporterDialog(wxWindow* parent, wxWindowID id, cons sectionSizer->Add(reportingSettingsSizer, 0, wxALL | wxEXPAND | wxFIXED_MINSIZE, 2); this->SetMinSize(GetBestSize()); + + // Menu bar + menuBar_ = new wxMenuBar(); + this->SetMenuBar(menuBar_); + + showMenu_ = new wxMenu(); + menuBar_->Append(showMenu_, _("Show")); + + std::vector > menus { + {CALLSIGN_COL, _("Callsign")}, + {GRID_SQUARE_COL, _("Locator")}, + {DISTANCE_COL, _("Distance")}, + {VERSION_COL, _("Version")}, + {HEADING_COL, _("Heading")}, + {FREQUENCY_COL, _("Frequency")}, + {TX_MODE_COL, _("TX Mode")}, + {STATUS_COL, _("Status")}, + {USER_MESSAGE_COL, _("User Message")}, + {LAST_TX_DATE_COL, _("Last TX Date")}, + {LAST_RX_CALLSIGN_COL, _("Last RX Callsign")}, + {LAST_RX_MODE_COL, _("Last RX Mode")}, + {SNR_COL, _("SNR")}, + {LAST_UPDATE_DATE_COL, _("Last Update")}, + }; + + for (auto& item : menus) + { + auto menuItem = showMenu_->Append(wxID_HIGHEST + item.first, item.second, wxEmptyString, wxITEM_CHECK); + menuItem->Check(wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnVisibility->at(item.first)); + this->Connect(wxID_HIGHEST + item.first, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(FreeDVReporterDialog::OnShowColumn)); + + if (item.first == LAST_RX_MODE_COL && !wxGetApp().appConfiguration.enableLegacyModes) + { + menuItem->Enable(false); + } + } // Trigger auto-layout of window. // ============================== @@ -440,6 +527,7 @@ FreeDVReporterDialog::FreeDVReporterDialog(wxWindow* parent, wxWindowID id, cons m_listSpots->Connect(wxEVT_MOTION, wxMouseEventHandler(FreeDVReporterDialog::AdjustToolTip), NULL, this); m_listSpots->Connect(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, wxDataViewEventHandler(FreeDVReporterDialog::OnItemRightClick), NULL, this); m_listSpots->Connect(wxEVT_DATAVIEW_COLUMN_HEADER_CLICK, wxDataViewEventHandler(FreeDVReporterDialog::OnColumnClick), NULL, this); + m_listSpots->Connect(wxEVT_DATAVIEW_COLUMN_REORDERED, wxDataViewEventHandler(FreeDVReporterDialog::OnColumnReordered), NULL, this); m_statusMessage->Connect(wxEVT_TEXT, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextChange), NULL, this); m_buttonSend->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextSend), NULL, this); @@ -506,7 +594,8 @@ FreeDVReporterDialog::~FreeDVReporterDialog() m_listSpots->Disconnect(wxEVT_MOTION, wxMouseEventHandler(FreeDVReporterDialog::AdjustToolTip), NULL, this); m_listSpots->Disconnect(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, wxDataViewEventHandler(FreeDVReporterDialog::OnItemRightClick), NULL, this); m_listSpots->Disconnect(wxEVT_DATAVIEW_COLUMN_HEADER_CLICK, wxDataViewEventHandler(FreeDVReporterDialog::OnColumnClick), NULL, this); - + m_listSpots->Disconnect(wxEVT_DATAVIEW_COLUMN_REORDERED, wxDataViewEventHandler(FreeDVReporterDialog::OnColumnReordered), NULL, this); + m_statusMessage->Disconnect(wxEVT_TEXT, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextChange), NULL, this); m_buttonSend->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextSend), NULL, this); m_buttonClear->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextClear), NULL, this); @@ -525,6 +614,23 @@ bool FreeDVReporterDialog::isTextMessageFieldInFocus() return m_statusMessage->HasFocus(); } +wxDataViewColumn* FreeDVReporterDialog::getColumnForModelColId_(unsigned int col) +{ + wxDataViewColumn* item = nullptr; + + for (unsigned int index = 0; index < m_listSpots->GetColumnCount(); index++) + { + item = m_listSpots->GetColumn(index); + if (item->GetModelColumn() == col) + { + break; + } + item = nullptr; + } + assert(item != nullptr); + return item; +} + void FreeDVReporterDialog::refreshLayout() { // Update row colors @@ -535,7 +641,7 @@ void FreeDVReporterDialog::refreshLayout() rxRowBackgroundColor = wxColour(wxGetApp().appConfiguration.reportingConfiguration.freedvReporterRxRowBackgroundColor); rxRowForegroundColor = wxColour(wxGetApp().appConfiguration.reportingConfiguration.freedvReporterRxRowForegroundColor); - wxDataViewColumn* item = m_listSpots->GetColumn(DISTANCE_COL); + wxDataViewColumn* item = getColumnForModelColId_(DISTANCE_COL); if (wxGetApp().appConfiguration.reportingConfiguration.useMetricDistances) { @@ -547,7 +653,7 @@ void FreeDVReporterDialog::refreshLayout() } // Refresh frequency units as appropriate. - item = m_listSpots->GetColumn(FREQUENCY_COL); + item = getColumnForModelColId_(FREQUENCY_COL); if (wxGetApp().appConfiguration.reportingConfiguration.reportingFrequencyAsKhz) { item->SetTitle("kHz"); @@ -558,7 +664,7 @@ void FreeDVReporterDialog::refreshLayout() } // Change direction/heading column label based on preferences - item = m_listSpots->GetColumn(HEADING_COL); + item = getColumnForModelColId_(HEADING_COL); if (wxGetApp().appConfiguration.reportingConfiguration.reportingDirectionAsCardinal) { item->SetTitle("Dir"); @@ -570,9 +676,11 @@ void FreeDVReporterDialog::refreshLayout() item->SetAlignment(wxALIGN_RIGHT); } - // Hide RX Mode column if legacy modes aren't enabled - auto rxModeColumn = m_listSpots->GetColumn(LAST_RX_MODE_COL); - rxModeColumn->SetHidden(!wxGetApp().appConfiguration.enableLegacyModes); + // Hide/show legacy columns + item = getColumnForModelColId_(LAST_RX_MODE_COL); + item->SetHidden(!wxGetApp().appConfiguration.enableLegacyModes || !wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnVisibility->at(LAST_RX_MODE_COL)); + auto menuItem = showMenu_->FindChildItem(wxID_HIGHEST + LAST_RX_MODE_COL); + menuItem->Enable(wxGetApp().appConfiguration.enableLegacyModes); // Refresh all data based on current settings and filters. FreeDVReporterDataModel* model = (FreeDVReporterDataModel*)spotsDataModel_.get(); @@ -596,6 +704,24 @@ void FreeDVReporterDialog::setReporter(std::shared_ptr const& re } } +void FreeDVReporterDialog::OnShowColumn(wxCommandEvent& event) +{ + // Invert visibility value + auto columnId = event.GetId() - wxID_HIGHEST; + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnVisibility->at(columnId) = + !wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnVisibility->at(columnId); + + auto newColValue = + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnVisibility->at(columnId); + + wxMenuItem* menuItem = static_cast(event.GetEventObject()); + menuItem->Check(newColValue); + + // Set column visibility in wxDataViewCtl. + auto col = getColumnForModelColId_(columnId); + col->SetHidden(!newColValue); +} + void FreeDVReporterDialog::DeselectItem(wxMouseEvent& event) { DeselectItem(); @@ -682,17 +808,23 @@ void FreeDVReporterDialog::OnOK(wxCommandEvent&) // Preserve sort column/ordering for (unsigned int index = 0; index < m_listSpots->GetColumnCount(); index++) { - auto colObj = m_listSpots->GetColumn(index); + auto colObj = getColumnForModelColId_(index); if (colObj != nullptr && colObj->IsSortKey()) { - wxGetApp().appConfiguration.reporterWindowCurrentSort = index; + wxGetApp().appConfiguration.reporterWindowCurrentSort = colObj->GetModelColumn(); wxGetApp().appConfiguration.reporterWindowCurrentSortDirection = colObj->IsSortOrderAscending(); break; } } - + + // Preserve column ordering +#if !defined(WIN32) + wxDataViewEvent tmp; + OnColumnReordered(tmp); +#endif // !defined(WIN32) + // Preserve Msg column width - auto userMsgCol = m_listSpots->GetColumn(USER_MESSAGE_COL); + auto userMsgCol = getColumnForModelColId_(USER_MESSAGE_COL); wxGetApp().appConfiguration.reportingUserMsgColWidth = userMsgCol->GetWidth(); wxGetApp().appConfiguration.reporterWindowVisible = false; @@ -727,11 +859,11 @@ void FreeDVReporterDialog::OnClose(wxCloseEvent&) bool found = false; for (unsigned int index = 0; index < m_listSpots->GetColumnCount(); index++) { - auto colObj = m_listSpots->GetColumn(index); + auto colObj = getColumnForModelColId_(index); if (colObj != nullptr && colObj->IsSortKey()) { found = true; - wxGetApp().appConfiguration.reporterWindowCurrentSort = index; + wxGetApp().appConfiguration.reporterWindowCurrentSort = colObj->GetModelColumn(); wxGetApp().appConfiguration.reporterWindowCurrentSortDirection = colObj->IsSortOrderAscending(); break; } @@ -743,8 +875,14 @@ void FreeDVReporterDialog::OnClose(wxCloseEvent&) wxGetApp().appConfiguration.reporterWindowCurrentSortDirection = true; } + // Preserve column ordering +#if !defined(WIN32) + wxDataViewEvent tmp; + OnColumnReordered(tmp); +#endif // !defined(WIN32) + // Preserve Msg column width - auto userMsgCol = m_listSpots->GetColumn(USER_MESSAGE_COL); + auto userMsgCol = getColumnForModelColId_(USER_MESSAGE_COL); wxGetApp().appConfiguration.reportingUserMsgColWidth = userMsgCol->GetWidth(); wxGetApp().appConfiguration.reporterWindowVisible = false; @@ -1091,6 +1229,45 @@ void FreeDVReporterDialog::SkipMouseEvent(wxMouseEvent&) OnItemRightClick(contextEvent); } +void FreeDVReporterDialog::OnColumnReordered(wxDataViewEvent&) +{ + // Preserve new column ordering + // Note: Windows uses the same indices for model column and GetColumn() + // so we need to use an alternate implementation for that platform. +#if defined(WIN32) + CallAfter([&]() { + std::vector newColPositions; + std::stringstream ss; + auto headerCtrl = m_listSpots->GenericGetHeader(); + wxArrayInt wxColumnOrder = headerCtrl->GetColumnsOrder(); + for (unsigned int index = 0; index < wxColumnOrder.GetCount(); index++) + { + auto col = wxColumnOrder.Item(index); + if (col < NUM_COLS) + { + newColPositions.push_back(col); + ss << col << " "; + } + } + + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder = newColPositions; + log_info("New column ordering: %s", ss.str().c_str()); + }); +#else + std::stringstream ss; + std::vector newColPositions; + for (unsigned int index = 0; index < NUM_COLS; index++) + { + auto dvc = m_listSpots->GetColumn(index); + newColPositions.push_back(dvc->GetModelColumn()); + ss << dvc->GetModelColumn() << " "; + } + + wxGetApp().appConfiguration.reportingConfiguration.freedvReporterColumnOrder = newColPositions; + log_info("New column ordering: %s", ss.str().c_str()); +#endif // defined(WIN32) +} + void FreeDVReporterDialog::OnColumnClick(wxDataViewEvent& event) { DeselectItem(); @@ -1398,7 +1575,7 @@ void FreeDVReporterDialog::autosizeColumns() if (index != USER_MESSAGE_COL) { // USER_MESSAGE_COL width is preserved and should not be messed with. - auto col = m_listSpots->GetColumn(index); + auto col = getColumnForModelColId_(index); col->SetWidth(wxCOL_WIDTH_DEFAULT); col->SetWidth(wxCOL_WIDTH_AUTOSIZE); } @@ -2526,8 +2703,8 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onFrequencyChangeFn_(std::st auto sortingColumn = parent_->m_listSpots->GetSortingColumn(); bool isChanged = - (sortingColumn == parent_->m_listSpots->GetColumn(FREQUENCY_COL) && iter->second->frequency != frequencyHz) || - (sortingColumn == parent_->m_listSpots->GetColumn(LAST_UPDATE_DATE_COL) && iter->second->lastUpdate != lastUpdateTime); + (sortingColumn == parent_->getColumnForModelColId_(FREQUENCY_COL) && iter->second->frequency != frequencyHz) || + (sortingColumn == parent_->getColumnForModelColId_(LAST_UPDATE_DATE_COL) && iter->second->lastUpdate != lastUpdateTime); bool isDataChanged = (iter->second->frequency != frequencyHz || iter->second->lastUpdate != lastUpdateTime); @@ -2609,8 +2786,8 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onTransmitUpdateFn_(std::str if (iter->second->status != _(RX_ONLY_STATUS)) { isChanged |= - (sortingColumn == parent_->m_listSpots->GetColumn(STATUS_COL) && iter->second->status != txStatus) || - (sortingColumn == parent_->m_listSpots->GetColumn(TX_MODE_COL) && iter->second->txMode != txMode); + (sortingColumn == parent_->getColumnForModelColId_(STATUS_COL) && iter->second->status != txStatus) || + (sortingColumn == parent_->getColumnForModelColId_(TX_MODE_COL) && iter->second->txMode != txMode); isDataChanged |= iter->second->status != txStatus || iter->second->txMode != txMode; @@ -2619,7 +2796,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onTransmitUpdateFn_(std::str iter->second->txMode = txMode; auto lastTxTime = makeValidTime_(lastTxDate, iter->second->lastTxDate); - isChanged |= (sortingColumn == parent_->m_listSpots->GetColumn(LAST_TX_DATE_COL) && iter->second->lastTx != lastTxTime); + isChanged |= (sortingColumn == parent_->getColumnForModelColId_(LAST_TX_DATE_COL) && iter->second->lastTx != lastTxTime); isDataChanged |= iter->second->lastTx != lastTxTime; iter->second->lastTx = lastTxTime; } @@ -2670,8 +2847,8 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onReceiveUpdateFn_(std::stri auto sortingColumn = parent_->m_listSpots->GetSortingColumn(); bool isChanged = - (sortingColumn == parent_->m_listSpots->GetColumn(LAST_RX_CALLSIGN_COL) && iter->second->lastRxCallsign != receivedCallsignWx) || - (sortingColumn == parent_->m_listSpots->GetColumn(LAST_RX_MODE_COL) && iter->second->lastRxMode != rxModeWx); + (sortingColumn == parent_->getColumnForModelColId_(LAST_RX_CALLSIGN_COL) && iter->second->lastRxCallsign != receivedCallsignWx) || + (sortingColumn == parent_->getColumnForModelColId_(LAST_RX_MODE_COL) && iter->second->lastRxMode != rxModeWx); bool isDataChanged = iter->second->lastRxCallsign != receivedCallsignWx || iter->second->lastRxMode != rxModeWx; @@ -2684,9 +2861,9 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onReceiveUpdateFn_(std::stri { // Frequency change--blank out SNR too. isChanged |= - (sortingColumn == parent_->m_listSpots->GetColumn(LAST_RX_CALLSIGN_COL) && iter->second->lastRxCallsign != parent_->UNKNOWN_STR) || - (sortingColumn == parent_->m_listSpots->GetColumn(LAST_RX_MODE_COL) && iter->second->lastRxMode != parent_->UNKNOWN_STR) || - (sortingColumn == parent_->m_listSpots->GetColumn(SNR_COL) && iter->second->snr != parent_->UNKNOWN_STR) || + (sortingColumn == parent_->getColumnForModelColId_(LAST_RX_CALLSIGN_COL) && iter->second->lastRxCallsign != parent_->UNKNOWN_STR) || + (sortingColumn == parent_->getColumnForModelColId_(LAST_RX_MODE_COL) && iter->second->lastRxMode != parent_->UNKNOWN_STR) || + (sortingColumn == parent_->getColumnForModelColId_(SNR_COL) && iter->second->snr != parent_->UNKNOWN_STR) || iter->second->lastRxDate.IsValid(); isDataChanged |= iter->second->lastRxCallsign != parent_->UNKNOWN_STR || @@ -2703,7 +2880,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onReceiveUpdateFn_(std::stri else { isChanged |= - (sortingColumn == parent_->m_listSpots->GetColumn(SNR_COL) && iter->second->snr != snrString); + (sortingColumn == parent_->getColumnForModelColId_(SNR_COL) && iter->second->snr != snrString); isDataChanged |= iter->second->snr != snrString; @@ -2753,13 +2930,13 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onMessageUpdateFn_(std::stri bool isChanged = false; if (message.size() == 0) { - isChanged |= (sortingColumn == parent_->m_listSpots->GetColumn(USER_MESSAGE_COL) && iter->second->userMessage != parent_->UNKNOWN_STR); + isChanged |= (sortingColumn == parent_->getColumnForModelColId_(USER_MESSAGE_COL) && iter->second->userMessage != parent_->UNKNOWN_STR); iter->second->userMessage = parent_->UNKNOWN_STR; } else { auto msgAsWxString = wxString::FromUTF8(message.c_str()); - isChanged |= (sortingColumn == parent_->m_listSpots->GetColumn(USER_MESSAGE_COL) && iter->second->userMessage != msgAsWxString); + isChanged |= (sortingColumn == parent_->getColumnForModelColId_(USER_MESSAGE_COL) && iter->second->userMessage != msgAsWxString); iter->second->userMessage = msgAsWxString; } diff --git a/src/gui/dialogs/freedv_reporter.h b/src/gui/dialogs/freedv_reporter.h index 59365eb1..29204735 100644 --- a/src/gui/dialogs/freedv_reporter.h +++ b/src/gui/dialogs/freedv_reporter.h @@ -112,6 +112,7 @@ class FreeDVReporterDialog : public wxFrame void OnColumnClick(wxDataViewEvent& event); void OnItemDoubleClick(wxDataViewEvent& event); void OnItemRightClick(wxDataViewEvent& event); + void OnColumnReordered(wxDataViewEvent& event); void OnTimer(wxTimerEvent& event); void DeselectItem(); @@ -122,6 +123,8 @@ class FreeDVReporterDialog : public wxFrame void SkipMouseEvent(wxMouseEvent& event); void AdjustMsgColWidth(wxListEvent& event); void OnRightClickSpotsList(wxContextMenuEvent& event); + + void OnShowColumn(wxCommandEvent& event); // Main list box that shows spots wxDataViewCtrl* m_listSpots; @@ -156,6 +159,10 @@ class FreeDVReporterDialog : public wxFrame wxTimer* m_deleteTimer; wxTipWindow* tipWindow_; + + // Menu bar and menu options + wxMenuBar* menuBar_; + wxMenu* showMenu_; private: const wxString UNKNOWN_STR; @@ -372,6 +379,9 @@ class FreeDVReporterDialog : public wxFrame bool isSelectionPossible_; + void createColumn_(int col, bool visible); + wxDataViewColumn* getColumnForModelColId_(unsigned int col); + FilterFrequency getFilterForFrequency_(uint64_t freq); wxColour msgRowBackgroundColor; wxColour msgRowForegroundColor; diff --git a/src/integrations/common/MinimalTxRxThread.cpp b/src/integrations/common/MinimalTxRxThread.cpp index 684c0e38..3d23b297 100644 --- a/src/integrations/common/MinimalTxRxThread.cpp +++ b/src/integrations/common/MinimalTxRxThread.cpp @@ -122,8 +122,8 @@ void* MinimalTxRxThread::Entry() #if defined(__linux__) const char* threadName = nullptr; - if (m_tx) threadName = "FreeDV txThread"; - else threadName = "FreeDV rxThread"; + if (m_tx) threadName = "FDV txThread"; + else threadName = "FDV rxThread"; pthread_setname_np(pthread_self(), threadName); #endif // defined(__linux__) diff --git a/src/main.cpp b/src/main.cpp index 950f3c0b..fa31892c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -861,7 +861,9 @@ setDefaultMode: m_textSQ->Enable(isEnabled); m_btnCenterRx->Enable(isEnabled); m_btnCenterRx->Show(wxGetApp().appConfiguration.enableLegacyModes); - + m_BtnReSync->Enable(isEnabled); + m_BtnReSync->Show(wxGetApp().appConfiguration.enableLegacyModes); + if (!isEnabled) { m_textBits->SetLabel("Bits: unk"); @@ -953,6 +955,7 @@ setDefaultMode: statsBox->Show(wxGetApp().appConfiguration.showDecodeStats); modeBox->Show(wxGetApp().appConfiguration.enableLegacyModes); + m_BtnReSync->Show(wxGetApp().appConfiguration.enableLegacyModes); // Initialize FreeDV Reporter as required CallAfter(&MainFrame::initializeFreeDVReporter_); @@ -1180,6 +1183,7 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent, wxID_ANY, _("FreeDV ") g_TxFreqOffsetHz = 0.0; g_tx.store(false, std::memory_order_release); + g_voice_keyer_tx.store(false, std::memory_order_release); // data states g_txDataInFifo.store(new GenericFIFO(MAX_CALLSIGN*FREEDV_VARICODE_MAX_BITS), std::memory_order_release); @@ -1416,6 +1420,7 @@ void MainFrame::OnTimer(wxTimerEvent &evt) short speechOutPlotSamples[WAVEFORM_PLOT_BUF]; short demodInPlotSamples[WAVEFORM_PLOT_BUF]; bool txState = false; + bool halfDuplexState = false; int syncState = 0; auto& timer = evt.GetTimer(); @@ -1429,6 +1434,7 @@ void MainFrame::OnTimer(wxTimerEvent &evt) if (timerId == ID_TIMER_UPDATE_OTHER) { txState = g_tx.load(std::memory_order_relaxed); + halfDuplexState = g_half_duplex.load(std::memory_order_relaxed); syncState_ = freedvInterface.getSync(); } syncState = syncState_; @@ -1515,14 +1521,24 @@ void MainFrame::OnTimer(wxTimerEvent &evt) // Update average magnitudes float rxSpectrum[MODEM_STATS_NSPEC]; memset(rxSpectrum, 0, sizeof(float) * MODEM_STATS_NSPEC); - while (g_avmag.numUsed() >= MODEM_STATS_NSPEC) + bool txNotInFullDuplex = halfDuplexState && txState; + if (!txNotInFullDuplex) { - g_avmag.read(rxSpectrum, MODEM_STATS_NSPEC); - for (int index = 0; index < MODEM_STATS_NSPEC; index++) + while (g_avmag.numUsed() >= MODEM_STATS_NSPEC) { - g_avmag_waterfall[index] = BETA * g_avmag_waterfall[index] + (1.0 - BETA) * rxSpectrum[index]; - } - memcpy(g_avmag_spectrum, g_avmag_waterfall, sizeof(g_avmag_waterfall)); + g_avmag.read(rxSpectrum, MODEM_STATS_NSPEC); + for (int index = 0; index < MODEM_STATS_NSPEC; index++) + { + g_avmag_waterfall[index] = BETA * g_avmag_waterfall[index] + (1.0 - BETA) * rxSpectrum[index]; + } + memcpy(g_avmag_spectrum, g_avmag_waterfall, sizeof(g_avmag_waterfall)); + } + } + else + { + // Assume zero spectrum to avoid waterfall artifacts + memset(g_avmag_waterfall, 0, sizeof(float) * MODEM_STATS_NSPEC); + memcpy(g_avmag_spectrum, g_avmag_waterfall, sizeof(g_avmag_waterfall)); } // Synchronize changes with Filter dialog @@ -2124,6 +2140,7 @@ void MainFrame::OnChangeTxMode( wxCommandEvent& event ) m_ckboxSQ->Enable(isEnabled); m_textSQ->Enable(isEnabled); m_btnCenterRx->Enable(isEnabled); + m_BtnReSync->Enable(isEnabled); } } @@ -2132,6 +2149,8 @@ void MainFrame::performFreeDVOn_() log_debug("Start ....."); g_queueResync = false; endingTx.store(false, std::memory_order_release); + g_voice_keyer_tx.store(false, std::memory_order_release); + g_tx.store(false, std::memory_order_release); m_timeSinceSyncLoss = 0; @@ -2281,7 +2300,7 @@ void MainFrame::performFreeDVOn_() }); g_State.store(0, std::memory_order_release); - g_prev_State.store(0, std::memory_order_release);; + g_prev_State.store(0, std::memory_order_release); g_snr = 0.0; g_half_duplex.store(wxGetApp().appConfiguration.halfDuplexMode, std::memory_order_release); diff --git a/src/ongui.cpp b/src/ongui.cpp index 5cf6c128..581ce92d 100644 --- a/src/ongui.cpp +++ b/src/ongui.cpp @@ -224,6 +224,8 @@ void MainFrame::OnToolsOptions(wxCommandEvent& event) m_textSQ->Enable(isEnabled); m_btnCenterRx->Enable(isEnabled); m_btnCenterRx->Show(wxGetApp().appConfiguration.enableLegacyModes); + m_BtnReSync->Enable(isEnabled); + m_BtnReSync->Show(wxGetApp().appConfiguration.enableLegacyModes); // XXX - with really short windows, wxWidgets sometimes doesn't size // the components properly until the user resizes the window (even if only @@ -383,7 +385,7 @@ void MainFrame::OnHelpManual( wxCommandEvent& ) //------------------------------------------------------------------------- void MainFrame::OnHelp( wxCommandEvent& ) { - wxLaunchDefaultBrowser("https://freedv.org/#gethelp"); + wxLaunchDefaultBrowser("https://freedv.org/#getting-help"); } //------------------------------------------------------------------------- diff --git a/src/topFrame.cpp b/src/topFrame.cpp index a13769db..4c56caf5 100644 --- a/src/topFrame.cpp +++ b/src/topFrame.cpp @@ -414,6 +414,10 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const wxMenuItem* m_menuItemHelpManual; m_menuItemHelpManual = new wxMenuItem(help, wxID_ANY, wxString(_("&User Manual...")), _("Loads the user manual"), wxITEM_NORMAL); help->Append(m_menuItemHelpManual); + + wxMenuItem* m_menuItemHelpGetAssistance; + m_menuItemHelpGetAssistance = new wxMenuItem(help, wxID_ANY, wxString(_("&Get Assistance")), _("Gets assistance with FreeDV setup"), wxITEM_NORMAL); + help->Append(m_menuItemHelpGetAssistance); m_menubarMain->Append(help, _("&Help")); @@ -553,15 +557,6 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const leftSizer->Add(sbSizer_ber,0, wxALL|wxEXPAND|wxFIXED_MINSIZE, 2); - //------------------------------ - // Help button: goes to Help page on website - //------------------------------ - wxStaticBox* helpBox = new wxStaticBox(m_panel, wxID_ANY, _("Assistance"), wxDefaultPosition, wxSize(100,-1)); - wxStaticBoxSizer* helpSizer = new wxStaticBoxSizer(helpBox, wxVERTICAL); - - m_btnHelp = new wxButton(helpBox, wxID_ANY, _("Get Help"), wxDefaultPosition, wxDefaultSize, 0); - m_btnHelp->SetToolTip(_("Get help with FreeDV.")); - helpSizer->Add(m_btnHelp, 0, wxALIGN_CENTER|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5); leftSizer->SetMinSize(wxSize(-1, 375)); #if !wxCHECK_VERSION(3,2,0) @@ -569,7 +564,6 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const #else leftOuterSizer->Add(leftSizer, 2, wxALL | wxEXPAND | wxFIXED_MINSIZE, 1); #endif // !wxCHECK_VERSION(3,2,0) - leftOuterSizer->Add(helpSizer, 0, wxFIXED_MINSIZE | wxALL | wxEXPAND, 1); bSizer1->Add(leftOuterSizer, 0, wxALL|wxEXPAND, 5); @@ -851,6 +845,7 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const this->Connect(m_menuItemHelpUpdates->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnHelpCheckUpdatesUI)); this->Connect(m_menuItemAbout->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnHelpAbout)); this->Connect(m_menuItemHelpManual->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnHelpManual)); + this->Connect(m_menuItemHelpGetAssistance->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnHelp)); m_sliderSQ->Connect(wxEVT_SCROLL_TOP, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); m_sliderSQ->Connect(wxEVT_SCROLL_BOTTOM, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); m_sliderSQ->Connect(wxEVT_SCROLL_LINEUP, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); @@ -874,7 +869,6 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const m_togBtnVoiceKeyer->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnVoiceKeyerRightClick), NULL, this); m_btnTogPTT->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnPTT), NULL, this); m_btnTogPTT->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnPTTRightClick), NULL, this); - m_btnHelp->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnHelp), NULL, this); m_BtnCallSignReset->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnCallSignReset), NULL, this); m_BtnBerReset->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnBerReset), NULL, this); @@ -968,7 +962,6 @@ TopFrame::~TopFrame() m_togBtnVoiceKeyer->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnVoiceKeyerRightClick), NULL, this); m_btnTogPTT->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnPTT), NULL, this); m_btnTogPTT->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnPTTRightClick), NULL, this); - m_btnHelp->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnHelp), NULL, this); m_btnCenterRx->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnCenterRx), NULL, this); diff --git a/src/topFrame.h b/src/topFrame.h index d4dcaf0b..085143f9 100644 --- a/src/topFrame.h +++ b/src/topFrame.h @@ -232,7 +232,6 @@ class TopFrame : public wxFrame wxToggleButton* m_togBtnAnalog; wxToggleButton* m_togBtnVoiceKeyer; wxToggleButton* m_btnTogPTT; - wxButton* m_btnHelp; wxAuiNotebook* m_auiNbookCtrl; wxComboBox* m_cboReportFrequency; wxStaticBox* m_freqBox;