Various main window and FreeDV Reporter UI improvements (#1132)

* Combine Locator and Distance columns.

* Pop up tooltip to show SW version instead of using an entire column.

* Add tooltip to better describe FreeDV Reporter tracking feature.

* Hide ReSync button (missed from previous PR).

* Move Assistance button to Help menu.

* Shorten thread names in integrations to match main app.

* Revert "Combine Locator and Distance columns."

This reverts commit 1c41913f51.

* Add PR #1132 to changelog.

* Make sure TX globals are properly initialized during startup.

* Allow Reporter columns to be moved by the user. Note: not persisting this yet.

* WIP column ordering persistence.

* Fix compiler error.

* Preserve sort ordering on Linux.

* Fix column sorting and naming bugs.:

* Allow columns to be hidden.

* Workaround due to previous bug, hopefully won't be necessary by merge.

* Make sure legacy column visibility is handled properly.

* Update changelog.

* Fix spelling mistake.

* Zero out waterfall when transmitting and not in full duplex. (#1136)

* Zero out waterfall when transmitting and not in full duplex.

* Add PR #1136 to changelog.

* latest user manual PDF

* Implement Windows specific column ordering save/restore.

* Fix Windows build error.

* Add additional debugging for Windows.

* Revert Windows-specific changes.

* Revert "Pop up tooltip to show SW version instead of using an entire column."

This reverts commit 55799509e1.

* Revert "Revert Windows-specific changes."

This reverts commit fa4bf18b8e.

* Windows: we need to get the new ordering after event handler's called.

* Forgot to allow Version column to be hidden.

* Better handle migration for columns that get added or removed.

* Fix most recent crash.

* Fix startup crash in GH environment.

* Fix Windows compiler error.

* Windows: need to make sure all columns in control are in the order list.

* Can't just push to a vector that's no longer in scope.

* Mirror files needed for Windows tests.

* Suppress possible cause of some of the Linux CI errors.
pull/1138/head
Mooneer Salem 2025-12-21 13:50:26 -08:00 committed by GitHub
parent c806ad3664
commit 612da148aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 521 additions and 188 deletions

View File

@ -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

View File

@ -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'

View File

@ -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!

View File

@ -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);

View File

@ -57,6 +57,9 @@ public:
ConfigurationDataElement<wxString> freedvReporterStatusText;
ConfigurationDataElement<std::vector<wxString> > freedvReporterRecentStatusTexts;
ConfigurationDataElement<std::vector<int> > freedvReporterColumnOrder;
ConfigurationDataElement<std::vector<bool> > freedvReporterColumnVisibility;
ConfigurationDataElement<bool> udpReportingEnabled;
ConfigurationDataElement<wxString> udpReportingHostname;
ConfigurationDataElement<int> udpReportingPort;

View File

@ -20,6 +20,7 @@
//==========================================================================
#include <wx/tokenzr.h>
#include <wx/numformatter.h>
#include "WxWidgetsConfigStore.h"
template<>
@ -30,6 +31,40 @@ void WxWidgetsConfigStore::load_<unsigned int>(wxConfigBase* config, Configurati
configElement.setWithoutProcessing((unsigned int)val);
}
template<>
void WxWidgetsConfigStore::load_<std::vector<int> >(wxConfigBase* config, ConfigurationDataElement<std::vector<int> >& configElement)
{
wxString val;
wxString defaultVal = generateStringFromArray_(configElement.getDefaultVal());
config->Read(configElement.getElementName(), &val, defaultVal);
configElement.setWithoutProcessing(generateNumArrayFromString_(val));
}
template<>
void WxWidgetsConfigStore::save_<std::vector<int> >(wxConfigBase* config, ConfigurationDataElement<std::vector<int> >& configElement)
{
wxString val = generateStringFromArray_(configElement.getWithoutProcessing());
config->Write(configElement.getElementName(), val);
}
template<>
void WxWidgetsConfigStore::load_<std::vector<bool> >(wxConfigBase* config, ConfigurationDataElement<std::vector<bool> >& configElement)
{
wxString val;
wxString defaultVal = generateStringFromArray_(configElement.getDefaultVal());
config->Read(configElement.getElementName(), &val, defaultVal);
configElement.setWithoutProcessing(generateBoolArrayFromString_(val));
}
template<>
void WxWidgetsConfigStore::save_<std::vector<bool> >(wxConfigBase* config, ConfigurationDataElement<std::vector<bool> >& 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_<std::vector<wxString> >(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_<std::vector<wxString> >(wxConfigBase* config, C
config->Write(configElement.getElementName(), val);
}
wxString WxWidgetsConfigStore::generateStringFromArray_(std::vector<int> 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<bool> const& vec)
{
std::vector<int> tmpVec;
tmpVec.reserve(vec.size());
for (auto v : vec)
{
tmpVec.push_back(v ? 1 : 0);
}
return generateStringFromArray_(tmpVec);
}
std::vector<bool> WxWidgetsConfigStore::generateBoolArrayFromString_(wxString const& str)
{
std::vector<int> tmpVec = generateNumArrayFromString_(str);
std::vector<bool> rv;
rv.reserve(tmpVec.size());
for (auto& v : tmpVec)
{
rv.push_back(v == 1 ? true : false);
}
return rv;
}
std::vector<int> WxWidgetsConfigStore::generateNumArrayFromString_(wxString const& str)
{
std::vector<int> 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<wxString> const& vec)
{
wxString rv = "";
@ -68,7 +164,7 @@ wxString WxWidgetsConfigStore::generateStringFromArray_(std::vector<wxString> co
return rv;
}
std::vector<wxString> WxWidgetsConfigStore::generateArayFromString_(wxString const& str)
std::vector<wxString> WxWidgetsConfigStore::generateStrArrayFromString_(wxString const& str)
{
std::vector<wxString> rv;

View File

@ -42,7 +42,13 @@ protected:
void save_(wxConfigBase* config, ConfigurationDataElement<UnderlyingDataType>& configElement);
wxString generateStringFromArray_(std::vector<wxString> const& vec);
std::vector<wxString> generateArayFromString_(wxString const& str);
std::vector<wxString> generateStrArrayFromString_(wxString const& str);
wxString generateStringFromArray_(std::vector<int> const& vec);
std::vector<int> generateNumArrayFromString_(wxString const& str);
wxString generateStringFromArray_(std::vector<bool> const& vec);
std::vector<bool> generateBoolArrayFromString_(wxString const& str);
};
template<typename UnderlyingDataType>
@ -68,4 +74,16 @@ void WxWidgetsConfigStore::load_<std::vector<wxString> >(wxConfigBase* config, C
template<>
void WxWidgetsConfigStore::save_<std::vector<wxString> >(wxConfigBase* config, ConfigurationDataElement<std::vector<wxString> >& configElement);
// Special handling for loading and saving int arrays.
template<>
void WxWidgetsConfigStore::load_<std::vector<int> >(wxConfigBase* config, ConfigurationDataElement<std::vector<int> >& configElement);
template<>
void WxWidgetsConfigStore::save_<std::vector<int> >(wxConfigBase* config, ConfigurationDataElement<std::vector<int> >& configElement);
// Special handling for loading and saving bool arrays.
template<>
void WxWidgetsConfigStore::load_<std::vector<bool> >(wxConfigBase* config, ConfigurationDataElement<std::vector<bool> >& configElement);
template<>
void WxWidgetsConfigStore::save_<std::vector<bool> >(wxConfigBase* config, ConfigurationDataElement<std::vector<bool> >& configElement);
#endif // WXWIDGETS_CONFIG_STORE_H

View File

@ -19,6 +19,7 @@
//
//==========================================================================
#include <sstream>
#include <math.h>
#include <wx/datetime.h>
#include <wx/display.h>
@ -29,6 +30,10 @@
#include <wx/uilocale.h>
#endif // wxCHECK_VERSION(3,2,0)
#if defined(WIN32)
#include <wx/headerctrl.h>
#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<std::pair<int, wxString> > 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<FreeDVReporter> 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<wxMenuItem*>(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<int> 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<int> 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;
}

View File

@ -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;

View File

@ -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__)

View File

@ -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<short>(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);

View File

@ -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");
}
//-------------------------------------------------------------------------

View File

@ -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);

View File

@ -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;