From 179e093262d87b1ad12b3294c85d6ef1ea596b10 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Tue, 20 Nov 2018 23:43:18 +0000 Subject: [PATCH 01/13] Improve handling of uncaught exceptions by catching them early and displaying in a message box --- main.cpp | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 784b709cd..601ae6a7e 100644 --- a/main.cpp +++ b/main.cpp @@ -52,6 +52,38 @@ namespace qsrand (seed); // this is good for rand() as well } } seeding; + + // We can't use the GUI after QApplication::exit() is called so + // uncaught exceptions can get lost on Windows systems where there + // is no console terminal, so here we override + // QApplication::notify() and wrap the base class call with a try + // block to catch and display exceptions in a message box. + class ExceptionCatchingApplication final + : public QApplication + { + public: + explicit ExceptionCatchingApplication (int& argc, char * * argv) + : QApplication {argc, argv} + { + } + bool notify (QObject * receiver, QEvent * e) override + { + try + { + return QApplication::notify (receiver, e); + } + catch (std::exception const& e) + { + MessageBox::critical_message (nullptr, translate ("main", "Fatal error"), e.what ()); + throw; + } + catch (...) + { + MessageBox::critical_message (nullptr, translate ("main", "Unexpected fatal error")); + throw; + } + } + }; } int main(int argc, char *argv[]) @@ -68,7 +100,7 @@ int main(int argc, char *argv[]) // Multiple instances communicate with jt9 via this QSharedMemory mem_jt9; - QApplication a(argc, argv); + ExceptionCatchingApplication a(argc, argv); try { setlocale (LC_NUMERIC, "C"); // ensure number forms are in @@ -339,12 +371,10 @@ int main(int argc, char *argv[]) } catch (std::exception const& e) { - MessageBox::critical_message (nullptr, a.translate ("main", "Fatal error"), e.what ()); std::cerr << "Error: " << e.what () << '\n'; } catch (...) { - MessageBox::critical_message (nullptr, a.translate ("main", "Unexpected fatal error")); std::cerr << "Unexpected fatal error\n"; throw; // hoping the runtime might tell us more about the exception } From 8fce78473fae17d52803a8a09e79b774b7fc32bc Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Tue, 20 Nov 2018 23:47:29 +0000 Subject: [PATCH 02/13] Switch to .cbr as the default Cabrillo file extension Should help to avoid accidents with other .log extension files in the WSJT-X log files directory. --- widgets/ExportCabrillo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widgets/ExportCabrillo.cpp b/widgets/ExportCabrillo.cpp index 928a60dc9..0e61f0f08 100644 --- a/widgets/ExportCabrillo.cpp +++ b/widgets/ExportCabrillo.cpp @@ -76,7 +76,7 @@ void ExportCabrillo::save_log () auto fname = QFileDialog::getSaveFileName (this , tr ("Save Log File") , configuration_->writeable_data_dir ().absolutePath () - , tr ("Cabrillo Log (*.log)")); + , tr ("Cabrillo Log (*.cbr)")); if (fname.size ()) { QFile f {fname}; From a488c64e43d181ec904574cd9e5074de53bb4a98 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Wed, 21 Nov 2018 01:32:31 +0000 Subject: [PATCH 03/13] Partially revert the merge below because of a bad merge of Configuration.ui "Merge tag 'wsjtx-2.0.0-rc3' into develop" This partially reverts commit e3f4efefe6128e586e3c2f31abb3e932e2862d2c, reversing changes made to 388fb94698193f26649cfbc482dde8b9931385f9. --- Configuration.ui | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Configuration.ui b/Configuration.ui index 1e8feae69..7a3ec96be 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -1712,24 +1712,24 @@ QListView::item:hover { - Some logging programs will not accept JT-65 or JT9 as a recognized mode. + Some logging programs will not accept the type of reports +saved by this program. +Check this option to save the sent and received reports in the +comments field. - Con&vert mode to RTTY + d&B reports to comments - <html><head/><body><p>The callsign of the operator, if different from the station callsign.</p></body></html> + Check this option to force the clearing of the DX Call +and DX Grid fields when a 73 or free text message is sent. - - - - - Log automatically + Clear &DX call and grid after logging From c81b3c8e6541cb0cfd4a657e912a92a72dce0ac8 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Fri, 23 Nov 2018 01:18:39 +0000 Subject: [PATCH 04/13] Validate contest QSO details before allowing logging Basic validation, must have non-empty exchange sent and received. Abstracted log view window widget behaviour into a base class. Turned on auto resize to row height in log view windows and enabled alternating colours. Convert empty fields to NULL when inserting new log table rows to signify missing data. Trap insert row errors when adding to contest log table so that logging can be held back if constraints are not met. Re-factored log QSO processing to try insert row into log table first and pop up a message box if constraints are not met, this pops up the Log QSO window in case it was initiated by an auto log event. --- CMakeLists.txt | 1 + models/CabrilloLog.cpp | 41 ++++++++++--- models/CabrilloLog.hpp | 2 +- models/FoxLog.cpp | 13 ++++- widgets/AbstractLogWindow.cpp | 76 ++++++++++++++++++++++++ widgets/AbstractLogWindow.hpp | 32 +++++++++++ widgets/CabrilloLogWindow.cpp | 105 ++++++++++++++++------------------ widgets/CabrilloLogWindow.hpp | 26 ++------- widgets/CabrilloLogWindow.ui | 20 ++++++- widgets/FoxLogWindow.cpp | 84 ++++++++------------------- widgets/FoxLogWindow.hpp | 23 ++------ widgets/FoxLogWindow.ui | 20 ++++++- widgets/logqso.cpp | 57 +++++++++++++----- widgets/logqso.h | 6 +- widgets/mainwindow.cpp | 40 +++++++------ widgets/widgets.pri | 10 ++-- 16 files changed, 349 insertions(+), 207 deletions(-) create mode 100644 widgets/AbstractLogWindow.cpp create mode 100644 widgets/AbstractLogWindow.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 39411f8a2..1cf9d88ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,7 @@ set (wsjt_qt_CXXSRCS models/DecodeHighlightingModel.cpp widgets/DecodeHighlightingListView.cpp models/FoxLog.cpp + widgets/AbstractLogWindow.cpp widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp item_delegates/CallsignDelegate.cpp diff --git a/models/CabrilloLog.cpp b/models/CabrilloLog.cpp index 055f8ae07..f447bca79 100644 --- a/models/CabrilloLog.cpp +++ b/models/CabrilloLog.cpp @@ -78,19 +78,46 @@ QAbstractItemModel * CabrilloLog::model () return &*m_; } -void CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString const& call +namespace +{ + void set_value_maybe_null (QSqlRecord& record, QString const& name, QString const& value) + { + if (value.size ()) + { + record.setValue (name, value); + } + else + { + record.setNull (name); + } + } +} + +bool CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString const& call , QString const& exchange_sent, QString const& exchange_received) { ConditionalTransaction transaction {*m_}; auto record = m_->record (); record.setValue ("frequency", frequency / 1000ull); // kHz - record.setValue ("when", when.toMSecsSinceEpoch () / 1000ull); - record.setValue ("call", call); - record.setValue ("exchange_sent", exchange_sent); - record.setValue ("exchange_rcvd", exchange_received); - record.setValue ("band", m_->configuration_->bands ()->find (frequency)); + if (!when.isNull ()) + { + record.setValue ("when", when.toMSecsSinceEpoch () / 1000ull); + } + else + { + record.setNull ("when"); + } + set_value_maybe_null (record, "call", call); + set_value_maybe_null (record, "exchange_sent", exchange_sent); + set_value_maybe_null (record, "exchange_rcvd", exchange_received); + set_value_maybe_null (record, "band", m_->configuration_->bands ()->find (frequency)); SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record); - transaction.submit (); + if (!transaction.submit (false)) + { + transaction.revert (); + return false; + } + return true; } bool CabrilloLog::dupe (Frequency frequency, QString const& call) const diff --git a/models/CabrilloLog.hpp b/models/CabrilloLog.hpp index 9571adeeb..e328d0633 100644 --- a/models/CabrilloLog.hpp +++ b/models/CabrilloLog.hpp @@ -21,7 +21,7 @@ public: ~CabrilloLog (); // returns false if insert fails - void add_QSO (Frequency, QDateTime const&, QString const& call + bool add_QSO (Frequency, QDateTime const&, QString const& call , QString const& report_sent, QString const& report_received); bool dupe (Frequency, QString const& call) const; diff --git a/models/FoxLog.cpp b/models/FoxLog.cpp index 81763d9be..43cdd2b8f 100644 --- a/models/FoxLog.cpp +++ b/models/FoxLog.cpp @@ -88,12 +88,19 @@ bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& { ConditionalTransaction transaction {*m_}; auto record = m_->record (); - record.setValue ("when", when.toMSecsSinceEpoch () / 1000); - record.setValue ("call", call); + if (!when.isNull ()) + { + record.setValue ("when", when.toMSecsSinceEpoch () / 1000); + } + else + { + record.setNull ("when"); + } + set_value_maybe_null (record, "call", call); set_value_maybe_null (record, "grid", grid); set_value_maybe_null (record, "report_sent", report_sent); set_value_maybe_null (record, "report_rcvd", report_received); - record.setValue ("band", band); + set_value_maybe_null (record, "band", band); SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record); if (!transaction.submit (false)) { diff --git a/widgets/AbstractLogWindow.cpp b/widgets/AbstractLogWindow.cpp new file mode 100644 index 000000000..ebc901cd8 --- /dev/null +++ b/widgets/AbstractLogWindow.cpp @@ -0,0 +1,76 @@ +#include "AbstractLogWindow.hpp" + +#include +#include +#include +#include +#include "Configuration.hpp" +#include "SettingsGroup.hpp" +#include "models/FontOverrideModel.hpp" +#include "pimpl_impl.hpp" + +class AbstractLogWindow::impl final +{ +public: + impl (QString const& settings_key, QSettings * settings, Configuration const * configuration) + : settings_key_ {settings_key} + , settings_ {settings} + , configuration_ {configuration} + , log_view_ {nullptr} + { + } + + QString settings_key_; + QSettings * settings_; + Configuration const * configuration_; + QTableView * log_view_; + FontOverrideModel model_; +}; + +AbstractLogWindow::AbstractLogWindow (QString const& settings_key, QSettings * settings + , Configuration const * configuration + , QWidget * parent) + : QWidget {parent} + , m_ {settings_key, settings, configuration} +{ + // ensure view scrolls to latest new row + connect (&m_->model_, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const& /*parent*/, int /*first*/, int /*last*/) { + if (m_->log_view_) m_->log_view_->scrollToBottom (); + }); +} + +AbstractLogWindow::~AbstractLogWindow () +{ + SettingsGroup g {m_->settings_, m_->settings_key_}; + m_->settings_->setValue ("window/geometry", saveGeometry ()); +} + +void AbstractLogWindow::set_log_model (QAbstractItemModel * log_model) +{ + m_->model_.setSourceModel (log_model); +} + +void AbstractLogWindow::set_log_view (QTableView * log_view) +{ + // do this here because we know the UI must be setup before this + SettingsGroup g {m_->settings_, m_->settings_key_}; + restoreGeometry (m_->settings_->value ("window/geometry").toByteArray ()); + + m_->log_view_ = log_view; + m_->log_view_->setModel (&m_->model_); + m_->log_view_->setColumnHidden (0, true); + auto horizontal_header = log_view->horizontalHeader (); + horizontal_header->setSectionResizeMode (QHeaderView::ResizeToContents); + horizontal_header->setSectionsMovable (true); + m_->log_view_->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + set_log_view_font (m_->configuration_->decoded_text_font ()); + m_->log_view_->scrollToBottom (); +} + +void AbstractLogWindow::set_log_view_font (QFont const& font) +{ + // m_->log_view_->setFont (font); + // m_->log_view_->horizontalHeader ()->setFont (font); + // m_->log_view_->verticalHeader ()->setFont (font); + m_->model_.set_font (font); +} diff --git a/widgets/AbstractLogWindow.hpp b/widgets/AbstractLogWindow.hpp new file mode 100644 index 000000000..35d5d27cf --- /dev/null +++ b/widgets/AbstractLogWindow.hpp @@ -0,0 +1,32 @@ +#ifndef ABSTRACT_LOG_WINDOW_HPP_ +#define ABSTRACT_LOG_WINDOW_HPP_ + +#include +#include "pimpl_h.hpp" + +class QString; +class QSettings; +class Configuration; +class QAbstractItemModel; +class QTableView; +class QFont; + +class AbstractLogWindow + : public QWidget +{ +public: + AbstractLogWindow (QString const& settings_key, QSettings * settings + , Configuration const * configuration + , QWidget * parent = nullptr); + virtual ~AbstractLogWindow () = 0; + + void set_log_model (QAbstractItemModel *); + void set_log_view (QTableView *); + void set_log_view_font (QFont const&); + +private: + class impl; + pimpl m_; +}; + +#endif diff --git a/widgets/CabrilloLogWindow.cpp b/widgets/CabrilloLogWindow.cpp index 4a86b4509..ab83a79f2 100644 --- a/widgets/CabrilloLogWindow.cpp +++ b/widgets/CabrilloLogWindow.cpp @@ -1,77 +1,68 @@ #include "CabrilloLogWindow.hpp" -#include #include -#include -#include -#include -#include -#include - -#include "SettingsGroup.hpp" +#include #include "Configuration.hpp" #include "models/Bands.hpp" #include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp" #include "item_delegates/CallsignDelegate.hpp" -#include "item_delegates/MaidenheadLocatorDelegate.hpp" -#include "widgets/MessageBox.hpp" -#include "qt_helpers.hpp" +#include "pimpl_impl.hpp" #include "ui_CabrilloLogWindow.h" +namespace +{ + class FormatProxyModel final + : public QIdentityProxyModel + { + public: + explicit FormatProxyModel (QObject * parent = nullptr) + : QIdentityProxyModel {parent} + { + } + + QVariant data (QModelIndex const& index, int role) const override + { + if (Qt::TextAlignmentRole == role && index.isValid ()) + { + switch (index.column ()) + { + case 1: + case 6: + return Qt::AlignRight + Qt::AlignVCenter; + default: + break; + } + } + return QIdentityProxyModel::data (index, role); + } + }; +} + +class CabrilloLogWindow::impl final +{ +public: + explicit impl () = default; + FormatProxyModel format_model_; + Ui::CabrilloLogWindow ui_; +}; + CabrilloLogWindow::CabrilloLogWindow (QSettings * settings, Configuration const * configuration , QAbstractItemModel * cabrillo_log_model, QWidget * parent) - : QWidget {parent} - , settings_ {settings} - , configuration_ {configuration} - , ui_ {new Ui::CabrilloLogWindow} + : AbstractLogWindow {"Cabrillo Log Window", settings, configuration, parent} { - cabrillo_log_model_.setSourceModel (cabrillo_log_model); setWindowTitle (QApplication::applicationName () + " - Cabrillo Log"); - ui_->setupUi (this); - read_settings (); - change_font (configuration_->decoded_text_font ()); - ui_->log_table_view->setModel (&cabrillo_log_model_); - ui_->log_table_view->setColumnHidden (0, true); - ui_->log_table_view->setItemDelegateForColumn (2, new DateTimeAsSecsSinceEpochDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (3, new CallsignDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration_->bands (), &cabrillo_log_model_, 0, 6, this}); - ui_->log_table_view->setSelectionMode (QTableView::SingleSelection); - auto horizontal_header = ui_->log_table_view->horizontalHeader (); - horizontal_header->setStretchLastSection (true); - horizontal_header->setSectionResizeMode (QHeaderView::ResizeToContents); - horizontal_header->setSectionsMovable (true); - horizontal_header->moveSection (6, 1); // band to first column - ui_->log_table_view->scrollToBottom (); - - // ensure view scrolls to latest new row - connect (&cabrillo_log_model_, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const& /*parent*/, int /*first*/, int /*last*/) { - ui_->log_table_view->scrollToBottom (); - }); + m_->ui_.setupUi (this); + m_->format_model_.setSourceModel (cabrillo_log_model); + set_log_model (&m_->format_model_); + set_log_view (m_->ui_.log_table_view); + m_->ui_.log_table_view->setItemDelegateForColumn (2, new DateTimeAsSecsSinceEpochDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (3, new CallsignDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), cabrillo_log_model, 0, 6, this}); + m_->ui_.log_table_view->horizontalHeader ()->moveSection (6, 1); // band to first column } CabrilloLogWindow::~CabrilloLogWindow () { - write_settings (); -} - -void CabrilloLogWindow::read_settings () -{ - SettingsGroup g {settings_, "Cabrillo Log Window"}; - restoreGeometry (settings_->value ("window/geometery").toByteArray ()); -} - -void CabrilloLogWindow::write_settings () const -{ - SettingsGroup g {settings_, "Cabrillo Log Window"}; - settings_->setValue ("window/geometery", saveGeometry ()); -} - -void CabrilloLogWindow::change_font (QFont const& font) -{ - // ui_->log_table_view->setFont (font); - // ui_->log_table_view->horizontalHeader ()->setFont (font); - // ui_->log_table_view->verticalHeader ()->setFont (font); - cabrillo_log_model_.set_font (font); } diff --git a/widgets/CabrilloLogWindow.hpp b/widgets/CabrilloLogWindow.hpp index 9a352ce9d..c0a964ce5 100644 --- a/widgets/CabrilloLogWindow.hpp +++ b/widgets/CabrilloLogWindow.hpp @@ -1,39 +1,25 @@ #ifndef CABRILLO_LOG_WINDOW_HPP_ #define CABRILLO_LOG_WINDOW_HPP_ -#include -#include -#include -#include "models/FontOverrideModel.hpp" +#include "AbstractLogWindow.hpp" +#include "pimpl_h.hpp" class QSettings; class Configuration; class QFont; -class QDateTime; class QAbstractItemModel; -namespace Ui -{ - class CabrilloLogWindow; -} class CabrilloLogWindow final - : public QWidget + : public AbstractLogWindow { public: explicit CabrilloLogWindow (QSettings *, Configuration const *, QAbstractItemModel * cabrillo_log_model - , QWidget * parent = nullptr); + , QWidget * parent = nullptr); ~CabrilloLogWindow (); - void change_font (QFont const&); - private: - void read_settings (); - void write_settings () const; - - QSettings * settings_; - Configuration const * configuration_; - FontOverrideModel cabrillo_log_model_; - QScopedPointer ui_; + class impl; + pimpl m_; }; #endif diff --git a/widgets/CabrilloLogWindow.ui b/widgets/CabrilloLogWindow.ui index eb25d1a6b..43061f132 100644 --- a/widgets/CabrilloLogWindow.ui +++ b/widgets/CabrilloLogWindow.ui @@ -2,12 +2,30 @@ CabrilloLogWindow + + + 0 + 0 + 274 + 210 + + Contest Log - + + + true + + + QAbstractItemView::SingleSelection + + + true + + diff --git a/widgets/FoxLogWindow.cpp b/widgets/FoxLogWindow.cpp index 252045359..f5e7e517d 100644 --- a/widgets/FoxLogWindow.cpp +++ b/widgets/FoxLogWindow.cpp @@ -1,12 +1,6 @@ #include "FoxLogWindow.hpp" -#include #include -#include -#include -#include -#include -#include #include "SettingsGroup.hpp" #include "Configuration.hpp" @@ -15,82 +9,50 @@ #include "item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp" #include "item_delegates/CallsignDelegate.hpp" #include "item_delegates/MaidenheadLocatorDelegate.hpp" -#include "widgets/MessageBox.hpp" -#include "qt_helpers.hpp" +#include "pimpl_impl.hpp" #include "ui_FoxLogWindow.h" +class FoxLogWindow::impl final +{ +public: + explicit impl () = default; + Ui::FoxLogWindow ui_; +}; + FoxLogWindow::FoxLogWindow (QSettings * settings, Configuration const * configuration , QAbstractItemModel * fox_log_model, QWidget * parent) - : QWidget {parent} - , settings_ {settings} - , configuration_ {configuration} - , ui_ {new Ui::FoxLogWindow} + : AbstractLogWindow {"Fox Log Window", settings, configuration, parent} { - fox_log_model_.setSourceModel (fox_log_model); setWindowTitle (QApplication::applicationName () + " - Fox Log"); - ui_->setupUi (this); - read_settings (); - change_font (configuration_->decoded_text_font ()); - ui_->log_table_view->setModel (&fox_log_model_); - ui_->log_table_view->setColumnHidden (0, true); - ui_->log_table_view->setItemDelegateForColumn (1, new DateTimeAsSecsSinceEpochDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (2, new CallsignDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (3, new MaidenheadLocatorDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration_->bands (), &fox_log_model_, 0, 6, this}); - ui_->log_table_view->setSelectionMode (QTableView::SingleSelection); - auto horizontal_header = ui_->log_table_view->horizontalHeader (); - horizontal_header->setStretchLastSection (true); - horizontal_header->setSectionResizeMode (QHeaderView::ResizeToContents); - horizontal_header->setSectionsMovable (true); - horizontal_header->moveSection (6, 1); // move band to first column - ui_->rate_label->setNum (0); - ui_->queued_label->setNum (0); - ui_->callers_label->setNum (0); - ui_->log_table_view->scrollToBottom (); - - // ensure view scrolls to latest new row - connect (&fox_log_model_, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const& /*parent*/, int /*first*/, int /*last*/) { - ui_->log_table_view->scrollToBottom (); - }); + m_->ui_.setupUi (this); + set_log_model (fox_log_model); + set_log_view (m_->ui_.log_table_view); + m_->ui_.log_table_view->setItemDelegateForColumn (1, new DateTimeAsSecsSinceEpochDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (2, new CallsignDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (3, new MaidenheadLocatorDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), fox_log_model, 0, 6, this}); + m_->ui_.log_table_view->horizontalHeader ()->moveSection (6, 1); // move band to first column + m_->ui_.rate_label->setNum (0); + m_->ui_.queued_label->setNum (0); + m_->ui_.callers_label->setNum (0); } FoxLogWindow::~FoxLogWindow () { - write_settings (); -} - -void FoxLogWindow::read_settings () -{ - SettingsGroup g {settings_, "Fox Log Window"}; - restoreGeometry (settings_->value ("window/geometery").toByteArray ()); -} - -void FoxLogWindow::write_settings () const -{ - SettingsGroup g {settings_, "Fox Log Window"}; - settings_->setValue ("window/geometery", saveGeometry ()); -} - -void FoxLogWindow::change_font (QFont const& font) -{ - // ui_->log_table_view->setFont (font); - // ui_->log_table_view->horizontalHeader ()->setFont (font); - // ui_->log_table_view->verticalHeader ()->setFont (font); - fox_log_model_.set_font (font); } void FoxLogWindow::callers (int n) { - ui_->callers_label->setNum (n); + m_->ui_.callers_label->setNum (n); } void FoxLogWindow::queued (int n) { - ui_->queued_label->setNum (n); + m_->ui_.queued_label->setNum (n); } void FoxLogWindow::rate (int n) { - ui_->rate_label->setNum (n); + m_->ui_.rate_label->setNum (n); } diff --git a/widgets/FoxLogWindow.hpp b/widgets/FoxLogWindow.hpp index 62895a763..d27ccdea6 100644 --- a/widgets/FoxLogWindow.hpp +++ b/widgets/FoxLogWindow.hpp @@ -1,42 +1,29 @@ #ifndef FOX_LOG_WINDOW_HPP_ #define FOX_LOG_WINDOW_HPP_ -#include -#include -#include -#include "models/FontOverrideModel.hpp" +#include "AbstractLogWindow.hpp" +#include "pimpl_h.hpp" class QSettings; class Configuration; class QFont; -class QDateTime; class QAbstractItemModel; -namespace Ui -{ - class FoxLogWindow; -} class FoxLogWindow final - : public QWidget + : public AbstractLogWindow { public: explicit FoxLogWindow (QSettings *, Configuration const *, QAbstractItemModel * fox_log_model , QWidget * parent = nullptr); ~FoxLogWindow (); - void change_font (QFont const&); void callers (int); void queued (int); void rate (int); private: - void read_settings (); - void write_settings () const; - - QSettings * settings_; - Configuration const * configuration_; - FontOverrideModel fox_log_model_; - QScopedPointer ui_; + class impl; + pimpl m_; }; #endif diff --git a/widgets/FoxLogWindow.ui b/widgets/FoxLogWindow.ui index 2a3e84aba..96ef8b112 100644 --- a/widgets/FoxLogWindow.ui +++ b/widgets/FoxLogWindow.ui @@ -2,12 +2,30 @@ FoxLogWindow + + + 0 + 0 + 331 + 238 + + Fox Log - + + + true + + + QAbstractItemView::SingleSelection + + + true + + diff --git a/widgets/logqso.cpp b/widgets/logqso.cpp index 77324e52b..b6412cb59 100644 --- a/widgets/logqso.cpp +++ b/widgets/logqso.cpp @@ -10,6 +10,7 @@ #include "MessageBox.hpp" #include "Configuration.hpp" #include "models/Bands.hpp" +#include "models/CabrilloLog.hpp" #include "validators/MaidenheadLocatorValidator.hpp" #include "ui_logqso.h" @@ -57,11 +58,10 @@ void LogQSO::storeSettings () const void LogQSO::initLogQSO(QString const& hisCall, QString const& hisGrid, QString mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, - Radio::Frequency dialFreq, bool noSuffix, QString xSent, QString xRcvd) + Radio::Frequency dialFreq, bool noSuffix, QString xSent, QString xRcvd, + CabrilloLog * cabrillo_log) { if(!isHidden()) return; - m_xSent=xSent; - m_xRcvd=xRcvd; ui->call->setText(hisCall); ui->grid->setText(hisGrid); ui->name->setText(""); @@ -87,17 +87,23 @@ void LogQSO::initLogQSO(QString const& hisCall, QString const& hisGrid, QString m_myGrid=m_config->my_grid(); ui->band->setText (m_config->bands ()->find (dialFreq)); ui->loggedOperator->setText(m_config->opCall()); - ui->exchSent->setText(m_xSent); - ui->exchRcvd->setText(m_xRcvd); + ui->exchSent->setText (xSent); + ui->exchRcvd->setText (xRcvd); + m_cabrilloLog = cabrillo_log; using SpOp = Configuration::SpecialOperatingActivity; - if( SpOp::FOX == m_config->special_op_id() or - (m_config->autoLog() and SpOp::NONE < m_config->special_op_id() and - m_xSent!="" and m_xRcvd!="")) { - accept(); - } else { - show(); - } + auto special_op = m_config->special_op_id (); + if (SpOp::FOX == special_op + || (m_config->autoLog () + && SpOp::NONE < special_op && special_op < SpOp::FOX)) + { + // allow auto logging in Fox mode and contests + accept(); + } + else + { + show(); + } } void LogQSO::accept() @@ -120,6 +126,29 @@ void LogQSO::accept() QString strDialFreq(QString::number(m_dialFreq / 1.e6,'f',6)); operator_call = ui->loggedOperator->text(); + // validate + using SpOp = Configuration::SpecialOperatingActivity; + auto special_op = m_config->special_op_id (); + if (SpOp::NONE < special_op && special_op < SpOp::FOX) + { + if (ui->exchSent->text ().isEmpty () || ui->exchRcvd->text ().isEmpty ()) + { + show (); + MessageBox::warning_message (this, tr ("Invalid QSO Data"), + tr ("Check exchange sent and received")); + return; // without accepting + } + + if (!m_cabrilloLog->add_QSO (m_dialFreq, QDateTime::currentDateTimeUtc (), hisCall, + ui->exchSent->text (), ui->exchRcvd->text ())) + { + show (); + MessageBox::warning_message (this, tr ("Invalid QSO Data"), + tr ("Check all fields")); + return; // without accepting + } + } + //Log this QSO to file "wsjtx.log" static QFile f {QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath ("wsjtx.log")}; if(!f.open(QIODevice::Text | QIODevice::Append)) { @@ -169,8 +198,8 @@ void LogQSO::accept() , m_myGrid , m_txPower , operator_call - , m_xSent - , m_xRcvd)); + , ui->exchSent->text () + , ui->exchRcvd->text ())); QDialog::accept(); } diff --git a/widgets/logqso.h b/widgets/logqso.h index 2f2e4c29e..b0e703a93 100644 --- a/widgets/logqso.h +++ b/widgets/logqso.h @@ -17,6 +17,7 @@ namespace Ui { class QSettings; class Configuration; class QByteArray; +class CabrilloLog; class LogQSO : public QDialog { @@ -28,7 +29,7 @@ public: void initLogQSO(QString const& hisCall, QString const& hisGrid, QString mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, Radio::Frequency dialFreq, - bool noSuffix, QString xSent, QString xRcvd); + bool noSuffix, QString xSent, QString xRcvd, CabrilloLog *); public slots: void accept(); @@ -56,10 +57,9 @@ private: Radio::Frequency m_dialFreq; QString m_myCall; QString m_myGrid; - QString m_xSent; - QString m_xRcvd; QDateTime m_dateTimeOn; QDateTime m_dateTimeOff; + CabrilloLog * m_cabrilloLog; }; #endif // LogQSO_H diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 186f5f40c..c1a09c227 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -1228,10 +1228,10 @@ void MainWindow::setDecodedTextFont (QFont const& font) m_msgAvgWidget->changeFont (font); } if (m_foxLogWindow) { - m_foxLogWindow->change_font (font); + m_foxLogWindow->set_log_view_font (font); } if (m_contestLogWindow) { - m_contestLogWindow->change_font (font); + m_contestLogWindow->set_log_view_font (font); } updateGeometry (); } @@ -5331,9 +5331,16 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button default: break; } + using SpOp = Configuration::SpecialOperatingActivity; + auto special_op = m_config.special_op_id (); + if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) + { + if (!m_cabrilloLog) m_cabrilloLog.reset (new CabrilloLog {&m_config}); + } m_logDlg->initLogQSO (m_hisCall, grid, m_modeTx, m_rptSent, m_rptRcvd, m_dateTimeQSOOn, dateTimeQSOOff, m_freqNominal + - ui->TxFreqSpinBox->value(), m_noSuffix, m_xSent, m_xRcvd); + ui->TxFreqSpinBox->value(), m_noSuffix, m_xSent, m_xRcvd, + m_cabrilloLog.data ()); } void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, QString const& grid @@ -5350,15 +5357,6 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, tr ("Cannot open \"%1\"").arg (m_logBook.path ())); } - - if (SpecOp::NONE < m_config.special_op_id() && SpecOp::FOX > m_config.special_op_id()) { - if (!m_cabrilloLog) m_cabrilloLog.reset (new CabrilloLog {&m_config}); - m_cabrilloLog->add_QSO (m_freqNominal, QDateTime::currentDateTimeUtc (), m_hisCall, m_xSent, m_xRcvd); - m_xSent=""; - m_xRcvd=""; - ui->sbSerialNumber->setValue (ui->sbSerialNumber->value () + 1); - } - m_messageClient->qso_logged (QSO_date_off, call, grid, dial_freq, mode, rpt_sent, rpt_received , tx_power, comments, name, QSO_date_on, operator_call, my_call, my_grid); m_messageClient->logged_ADIF (ADIF); @@ -5378,6 +5376,15 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, if (m_config.clear_DX () and SpecOp::HOUND != m_config.special_op_id()) clearDX (); m_dateTimeQSOOn = QDateTime {}; + using SpOp = Configuration::SpecialOperatingActivity; + auto special_op = m_config.special_op_id (); + if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) + { + ui->sbSerialNumber->setValue (ui->sbSerialNumber->value () + 1); + } + + m_xSent.clear (); + m_xRcvd.clear (); } qint64 MainWindow::nWidgets(QString t) @@ -6125,11 +6132,12 @@ void MainWindow::on_reset_fox_log_action_triggered () void MainWindow::on_reset_cabrillo_log_action_triggered () { - if (MessageBox::Yes == MessageBox::query_message(this, tr("Confirm Erase"), - tr("Are you sure you want to erase file cabrillo.log" - " and start a new Cabrillo log?"))) + if (MessageBox::Yes == MessageBox::query_message (this, tr ("Confirm Reset"), + tr ("Are you sure you want to erase your contest log?"), + tr ("Doing this will remove all QSO records for the current contest. " + "They will be kept in the ADIF log file but will not be available " + "for export in your Cabrillo log."))) { - QFile {m_config.writeable_data_dir ().absoluteFilePath ("cabrillo.log")}.remove (); ui->sbSerialNumber->setValue (1); if (!m_cabrilloLog) m_cabrilloLog.reset (new CabrilloLog {&m_config}); m_cabrilloLog->reset (); diff --git a/widgets/widgets.pri b/widgets/widgets.pri index 019fdcd17..e70c5cb9e 100644 --- a/widgets/widgets.pri +++ b/widgets/widgets.pri @@ -7,7 +7,8 @@ SOURCES += \ widgets/echoplot.cpp widgets/echograph.cpp widgets/fastgraph.cpp \ widgets/fastplot.cpp widgets/MessageBox.cpp \ widgets/colorhighlighting.cpp widgets/ExportCabrillo.cpp \ - widgets/CabrilloLogWindow.cpp + widgets/AbstractLogWindow.cpp \ + widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp HEADERS += \ widgets/mainwindow.h widgets/plotter.h \ @@ -17,8 +18,8 @@ HEADERS += \ widgets/meterwidget.h widgets/messageaveraging.h \ widgets/echoplot.h widgets/echograph.h widgets/fastgraph.h \ widgets/fastplot.h widgets/MessageBox.hpp widgets/colorhighlighting.h \ - widgets/ExportCabrillo.h \ - widgets/CabrilloLogWindow.cpp + widgets/ExportCabrillo.h widgets/AbstractLogWindow.hpp \ + widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp FORMS += \ widgets/mainwindow.ui widgets/about.ui \ @@ -26,5 +27,4 @@ FORMS += \ widgets/logqso.ui widgets/messageaveraging.ui \ widgets/echograph.ui widgets/fastgraph.ui \ widgets/colorhighlighting.ui widgets/ExportCabrillo.ui \ - widgets/FoxLogWindow.ui \ - widgets/CabrilloLogWindow.ui + widgets/FoxLogWindow.ui widgets/CabrilloLogWindow.ui From e434bc5b550ec34632cfa2b9fc46beb3641cf363 Mon Sep 17 00:00:00 2001 From: Steve Franke Date: Fri, 23 Nov 2018 15:10:44 -0600 Subject: [PATCH 05/13] Remove obsolete routines related to msk144. --- CMakeLists.txt | 13 +-- lib/Makefile.mskWin | 75 --------------- lib/encode_msk144.f90 | 111 --------------------- lib/extractmessage144.f90 | 51 ---------- lib/genmsk144.f90 | 146 ---------------------------- lib/genmsk_short.f90 | 66 ------------- lib/ldpcsim144.f90 | 175 --------------------------------- lib/msk144d.f90 | 136 -------------------------- lib/msk144sd.f90 | 197 -------------------------------------- lib/msk144sim.f90 | 21 ++-- lib/platanh.f90 | 24 +++++ lib/pltanh.f90 | 24 +++++ lib/unpackmsg144.f90 | 117 ---------------------- 13 files changed, 59 insertions(+), 1097 deletions(-) delete mode 100644 lib/Makefile.mskWin delete mode 100644 lib/encode_msk144.f90 delete mode 100644 lib/extractmessage144.f90 delete mode 100644 lib/genmsk144.f90 delete mode 100644 lib/genmsk_short.f90 delete mode 100644 lib/ldpcsim144.f90 delete mode 100644 lib/msk144d.f90 delete mode 100644 lib/msk144sd.f90 create mode 100644 lib/platanh.f90 create mode 100644 lib/pltanh.f90 delete mode 100644 lib/unpackmsg144.f90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 39411f8a2..5818cc8b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,6 @@ set (wsjt_FSRCS lib/badmsg.f90 lib/ft8/baseline.f90 lib/bpdecode40.f90 - lib/bpdecode144.f90 lib/bpdecode128_90.f90 lib/ft8/bpdecode174.f90 lib/ft8/bpdecode174_91.f90 @@ -414,7 +413,6 @@ set (wsjt_FSRCS lib/encode232.f90 lib/encode4.f90 lib/encode_msk40.f90 - lib/encode_msk144.f90 lib/encode_128_90.f90 lib/ft8/encode174.f90 lib/ft8/encode174_91.f90 @@ -422,7 +420,6 @@ set (wsjt_FSRCS lib/ephem.f90 lib/extract.f90 lib/extract4.f90 - lib/extractmessage144.f90 lib/extractmessage77.f90 lib/ft8/extractmessage174.f90 lib/ft8/extractmessage174_91.f90 @@ -497,10 +494,8 @@ set (wsjt_FSRCS lib/moondopjpl.f90 lib/morse.f90 lib/move.f90 - lib/msk144d.f90 lib/msk40decodeframe.f90 lib/msk144decodeframe.f90 - lib/msk144sd.f90 lib/msk40spd.f90 lib/msk144spd.f90 lib/msk40sync.f90 @@ -519,6 +514,8 @@ set (wsjt_FSRCS lib/peakdt9.f90 lib/peakup.f90 lib/plotsave.f90 + lib/platanh.f90 + lib/pltanh.f90 lib/polyfit.f90 lib/prog_args.f90 lib/ps4.f90 @@ -561,7 +558,6 @@ set (wsjt_FSRCS lib/twkfreq.f90 lib/ft8/twkfreq1.f90 lib/twkfreq65.f90 - lib/unpackmsg144.f90 lib/update_recent_calls.f90 lib/update_hasharray.f90 lib/ft8/watterson.f90 @@ -1252,14 +1248,9 @@ target_link_libraries (ft8code wsjt_fort wsjt_cxx) add_executable (ft8sim lib/ft8/ft8sim.f90 wsjtx.rc) target_link_libraries (ft8sim wsjt_fort wsjt_cxx) -add_executable (msk144sd lib/msk144sd.f90 wsjtx.rc) -target_link_libraries (msk144sd wsjt_fort wsjt_cxx) - add_executable (msk144sim lib/msk144sim.f90 wsjtx.rc) target_link_libraries (msk144sim wsjt_fort wsjt_cxx) -add_executable (msk144d lib/msk144d.f90 wsjtx.rc) -target_link_libraries (msk144d wsjt_fort wsjt_cxx) endif(WSJT_BUILD_UTILS) # build the main application diff --git a/lib/Makefile.mskWin b/lib/Makefile.mskWin deleted file mode 100644 index 9d5e6e62b..000000000 --- a/lib/Makefile.mskWin +++ /dev/null @@ -1,75 +0,0 @@ - -# Set paths -EXE_DIR = ..\\..\\wsjtx_install -QT_DIR = C:/wsjt-env/Qt5/5.2.1/mingw48_32 -FFTW3_DIR = .. - -INCPATH = -I${QT_DIR}/include/QtCore -I${QT_DIR}/include - -# Compilers -CC = gcc -CXX = g++ -FC = gfortran -AR = ar cr -RANLIB = ranlib -MKDIR = mkdir -p -CP = cp -RM = rm -f - -FFLAGS = -O2 -fbounds-check -Wall -Wno-conversion -CFLAGS = -O2 -I. - -# Default rules -%.o: %.c - ${CC} ${CFLAGS} -c $< -%.o: %.f - ${FC} ${FFLAGS} -c $< -%.o: %.F - ${FC} ${FFLAGS} -c $< -%.o: %.f90 - ${FC} ${FFLAGS} -c $< -%.o: %.F90 - ${FC} ${FFLAGS} -c $< - -#all: jt9code JTMSKcode.exe -all: jtmsk.exe JTMSKsim.exe JTMSKcode.exe fixwav.exe - -OBJS3 = JTMSKsim.o wavhdr.o gran.o four2a.o db.o -JTMSKsim.exe: $(OBJS3) - $(FC) -o JTMSKsim.exe $(OBJS3) C:\JTSDK\fftw3f\libfftw3f-3.dll - -OBJS4 = jt9code.o packjt.o fmtmsg.o gen9.o deg2grid.o grid2deg.o \ - entail.o encode232.o interleave9.o graycode.o igray.o -jt9code: $(OBJS4) - $(FC) -o jt9code $(OBJS4) - -OBJS5 = JTMSKcode.o packjt.o fmtmsg.o genmsk.o deg2grid.o grid2deg.o \ - entail.o tab.o vit213.o hashing.o nhash.o -JTMSKcode.exe: $(OBJS5) - $(FC) -o JTMSKcode.exe $(OBJS5) - -OBJS6 = jtmsk.o analytic.o four2a.o db.o pctile.o \ - shell.o tweak1.o syncmsk.o genmsk.o packjt.o fmtmsg.o indexx.o \ - deg2grid.o grid2deg.o entail.o hashing.o nhash.o tab.o vit213.o \ - mskdt.o rectify_msk.o timer.o jtmsk_decode.o genmsk_short.o \ - jtmsk_short.o golay24_table.o hash.o - -jtmsk.exe: $(OBJS6) - $(FC) -o jtmsk.exe $(OBJS6) C:\JTSDK\fftw3f\libfftw3f-3.dll - -OBJS1 = fixwav.o wavhdr.o -fixwav.exe: $(OBJS1) - $(FC) -o fixwav.exe $(OBJS1) - -OBJS2 = t2.o four2a.o db.o -t2: $(OBJS2) - $(FC) -o t2 $(OBJS2) C:\JTSDK\fftw3f\libfftw3f-3.dll - -OBJS6 = t6.o four2a.o db.o -t6: $(OBJS6) - $(FC) -o t6 $(OBJS6) C:\JTSDK\fftw3f\libfftw3f-3.dll - -.PHONY : clean - -clean: - $(RM) *.o JTMSKcode JTMSKcode.exe diff --git a/lib/encode_msk144.f90 b/lib/encode_msk144.f90 deleted file mode 100644 index 4e4d8968e..000000000 --- a/lib/encode_msk144.f90 +++ /dev/null @@ -1,111 +0,0 @@ -subroutine encode_msk144(message,codeword) -! Encode an 80-bit message and return a 128-bit codeword. -! The generator matrix has dimensions (48,80). -! The code is a (128,80) regular ldpc code with column weight 3. -! The code was generated using the PEG algorithm. -! After creating the codeword, the columns are re-ordered according to -! "colorder" to make the codeword compatible with the parity-check -! matrix stored in Radford Neal's "pchk" format. -! -character*20 g(48) -integer*1 codeword(128) -integer*1 colorder(128) -integer*1 gen144(48,80) -integer*1 itmp(128) -integer*1 message(80) -integer*1 pchecks(48) -logical first -data first/.true./ -data g/ & !parity-check generator matrix for (128,80) code - "24084000800020008000", & - "b39678f7ccdb1baf5f4c", & - "10001000400408012000", & - "08104000100002010800", & - "dc9c18f61ea0e4b7f05c", & - "42c040160909ca002c00", & - "cc50b52b9a80db0d7f9e", & - "dde5ace80780bae74740", & - "00800080020000890080", & - "01020040010400400040", & - "20008010020000100030", & - "80400008004000040050", & - "a4b397810915126f5604", & - "04040100001040200008", & - "00800006000888000800", & - "00010c00000104040001", & - "cc7cd7d953cdc204eba0", & - "0094abe7dd146beb16ce", & - "5af2aec8c7b051c7544a", & - "14040508801840200088", & - "7392f5e720f8f5a62c1e", & - "503cc2a06bff4e684ec9", & - "5a2efd46f1efbb513b80", & - "ac06e9513fd411f1de03", & - "16a31be3dd3082ca2bd6", & - "28542e0daf62fe1d9332", & - "00210c002001540c0401", & - "0ed90d56f84298706a98", & - "939670f7ecdf9baf4f4c", & - "cfe41dec47a433e66240", & - "16d2179c2d5888222630", & - "408000160108ca002800", & - "808000830a00018900a0", & - "9ae2ed8ef3afbf8c3a52", & - "5aaafd86f3efbfc83b02", & - "f39658f68cdb0baf1f4c", & - "9414bb6495106261366a", & - "71ba18670c08411bf682", & - "7298f1a7217cf5c62e5e", & - "86d7a4864396a981369b", & - "a8042c01ae22fe191362", & - "9235ae108b2d60d0e306", & - "dfe5ade807a03be74640", & - "d2451588e6e27ccd9bc4", & - "12b51ae39d20e2ea3bde", & - "a49387810d95136fd604", & - "467e7578e51d5b3b8a0e", & - "f6ad1ac7cc3aaa3fe580"/ - -data colorder/0,1,2,3,4,5,6,7,8,9, & - 10,11,12,13,14,15,24,26,29,30, & - 32,43,44,47,60,77,79,97,101,111, & - 96,38,64,53,93,34,59,94,74,90, & - 108,123,85,57,70,25,69,62,48,49, & - 50,51,52,33,54,55,56,21,58,36, & - 16,61,23,63,20,65,66,67,68,46, & - 22,71,72,73,31,75,76,45,78,17, & - 80,81,82,83,84,42,86,87,88,89, & - 39,91,92,35,37,95,19,27,98,99, & - 100,28,102,103,104,105,106,107,40,109, & - 110,18,112,113,114,115,116,117,118,119, & - 120,121,122,41,124,125,126,127/ - -save first,gen144 - -if( first ) then ! fill the generator matrix - gen144=0 - do i=1,48 - do j=1,5 - read(g(i)( (j-1)*4+1:(j-1)*4+4 ),"(Z4)") istr - do jj=1,16 - icol=(j-1)*16+jj - if( btest(istr,16-jj) ) gen144(i,icol)=1 - enddo - enddo - enddo -first=.false. -endif - -do i=1,48 - nsum=0 - do j=1,80 - nsum=nsum+message(j)*gen144(i,j) - enddo - pchecks(i)=mod(nsum,2) -enddo -itmp(1:48)=pchecks -itmp(49:128)=message(1:80) -codeword(colorder+1)=itmp(1:128) - -return -end subroutine encode_msk144 diff --git a/lib/extractmessage144.f90 b/lib/extractmessage144.f90 deleted file mode 100644 index a5dadb0b4..000000000 --- a/lib/extractmessage144.f90 +++ /dev/null @@ -1,51 +0,0 @@ -subroutine extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent) - use iso_c_binding, only: c_loc,c_size_t - use packjt - use hashing - - character*22 msgreceived - character*12 call1,call2 - character*12 recent_calls(nrecent) - integer*1 decoded(80) - integer*1, target:: i1Dec8BitBytes(10) - integer*1 i1hashdec - integer*4 i4Dec6BitWords(12) - -! Collapse 80 decoded bits to 10 bytes. Bytes 1-9 are the message, byte 10 is the hash - do ibyte=1,10 - itmp=0 - do ibit=1,8 - itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*8+ibit)) - enddo - i1Dec8BitBytes(ibyte)=itmp - enddo - -! Calculate the hash using the first 9 bytes. - ihashdec=nhash(c_loc(i1Dec8BitBytes),int(9,c_size_t),146) - ihashdec=2*iand(ihashdec,255) - -! Compare calculated hash with received byte 10 - if they agree, keep the message. - i1hashdec=ihashdec - if( i1hashdec .eq. i1Dec8BitBytes(10) ) then -! Good hash --- unpack 72-bit message - do ibyte=1,12 - itmp=0 - do ibit=1,6 - itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*6+ibit)) - enddo - i4Dec6BitWords(ibyte)=itmp - enddo - call unpackmsg144(i4Dec6BitWords,msgreceived,call1,call2) - nhashflag=1 - if( call1(1:2) .ne. 'CQ' .and. call1(1:2) .ne. ' ' ) then - call update_recent_calls(call1,recent_calls,nrecent) - endif - if( call2(1:2) .ne. ' ' ) then - call update_recent_calls(call2,recent_calls,nrecent) - endif - else - msgreceived=' ' - nhashflag=-1 - endif - return - end subroutine extractmessage144 diff --git a/lib/genmsk144.f90 b/lib/genmsk144.f90 deleted file mode 100644 index f02344439..000000000 --- a/lib/genmsk144.f90 +++ /dev/null @@ -1,146 +0,0 @@ -subroutine genmsk144(msg0,mygrid,ichk,msgsent,i4tone,itype) -! s8 + 48bits + s8 + 80 bits = 144 bits (72ms message duration) -! -! Encode an MSK144 message -! Input: -! - msg0 requested message to be transmitted -! - ichk if ichk=1, return only msgsent -! if ichk.ge.10000, set imsg=ichk-10000 for short msg -! - msgsent message as it will be decoded -! - i4tone array of audio tone values, 0 or 1 -! - itype message type -! 1 = standard message "Call_1 Call_2 Grid/Rpt" -! 2 = type 1 prefix -! 3 = type 1 suffix -! 4 = type 2 prefix -! 5 = type 2 suffix -! 6 = free text (up to 13 characters) -! 7 = short message " Rpt" - - use iso_c_binding, only: c_loc,c_size_t - use packjt - use hashing - character*22 msg0 - character*22 message !Message to be generated - character*22 msgsent !Message as it will be received - character*6 mygrid - integer*4 i4Msg6BitWords(13) !72-bit message as 6-bit words - integer*4 i4tone(144) ! - integer*1, target:: i1Msg8BitBytes(10) !80 bits represented in 10 bytes - integer*1 codeword(128) !Encoded bits before re-ordering - integer*1 msgbits(80) !72-bit message + 8-bit hash - integer*1 bitseq(144) !Tone #s, data and sync (values 0-1) - integer*1 i1hash(4) - integer*1 s8(8) - real*8 pp(12) - real*8 xi(864),xq(864),pi,twopi - data s8/0,1,1,1,0,0,1,0/ - equivalence (ihash,i1hash) - logical first - data first/.true./ - save - - if(first) then - first=.false. - nsym=128 - pi=4.0*atan(1.0) - twopi=8.*atan(1.0) - do i=1,12 - pp(i)=sin((i-1)*pi/12) - enddo - endif - - if(msg0(1:1).eq.'@') then !Generate a fixed tone - read(msg0(2:5),*,end=1,err=1) nfreq !at specified frequency - go to 2 -1 nfreq=1000 -2 i4tone(1)=nfreq - else - message=msg0 - do i=1,22 - if(ichar(message(i:i)).eq.0) then - message(i:)=' ' - exit - endif - enddo - - do i=1,22 !Strip leading blanks - if(message(1:1).ne.' ') exit - message=message(i+1:) - enddo - - if(message(1:1).eq.'<') then - call genmsk40(message,msgsent,ichk,i4tone,itype) - if(itype.lt.0) go to 999 - i4tone(41)=-40 - go to 999 - endif - - call packmsg(message,i4Msg6BitWords,itype) !Pack into 12 6-bit bytes - call unpackmsg(i4Msg6BitWords,msgsent) !Unpack to get msgsent - - if(ichk.eq.1) go to 999 - i4=0 - ik=0 - im=0 - do i=1,12 - nn=i4Msg6BitWords(i) - do j=1, 6 - ik=ik+1 - i4=i4+i4+iand(1,ishft(nn,j-6)) - i4=iand(i4,255) - if(ik.eq.8) then - im=im+1 - i1Msg8BitBytes(im)=i4 - ik=0 - endif - enddo - enddo - - ihash=nhash(c_loc(i1Msg8BitBytes),int(9,c_size_t),146) - ihash=2*iand(ihash,32767) !Generate the 8-bit hash - i1Msg8BitBytes(10)=i1hash(1) !Hash code to byte 10 - - mbit=0 - do i=1, 10 - i1=i1Msg8BitBytes(i) - do ibit=1,8 - mbit=mbit+1 - msgbits(mbit)=iand(1,ishft(i1,ibit-8)) - enddo - enddo - - call encode_msk144(msgbits,codeword) - -!Create 144-bit channel vector: -!8-bit sync word + 48 bits + 8-bit sync word + 80 bits - bitseq=0 - bitseq(1:8)=s8 - bitseq(9:56)=codeword(1:48) - bitseq(57:64)=s8 - bitseq(65:144)=codeword(49:128) - bitseq=2*bitseq-1 - - xq(1:6)=bitseq(1)*pp(7:12) !first bit is mapped to 1st half-symbol on q - do i=1,71 - is=(i-1)*12+7 - xq(is:is+11)=bitseq(2*i+1)*pp - enddo - xq(864-5:864)=bitseq(1)*pp(1:6) !last half symbol - do i=1,72 - is=(i-1)*12+1 - xi(is:is+11)=bitseq(2*i)*pp - enddo -! Map I and Q to tones. - i4tone=0 - do i=1,72 - i4tone(2*i-1)=(bitseq(2*i)*bitseq(2*i-1)+1)/2; - i4tone(2*i)=-(bitseq(2*i)*bitseq(mod(2*i,144)+1)-1)/2; - enddo - endif - -! Flip polarity - i4tone=-i4tone+1 - -999 return -end subroutine genmsk144 diff --git a/lib/genmsk_short.f90 b/lib/genmsk_short.f90 deleted file mode 100644 index f1b555f34..000000000 --- a/lib/genmsk_short.f90 +++ /dev/null @@ -1,66 +0,0 @@ -subroutine genmsk_short(msg,msgsent,ichk,itone,itype) - - use hashing - character*22 msg,msgsent - character*3 crpt,rpt(0:7) - logical first - integer itone(35) - integer ig24(0:4096-1) !Codewords for Golay (24,12) code - integer b11(11) - data b11/1,1,1,0,0,0,1,0,0,1,0/ !Barker 11 code - data rpt /'26 ','27 ','28 ','R26','R27','R28','RRR','73 '/ - data first/.true./ - save first,ig24 - - if(first) then - call golay24_table(ig24) !Define the Golay(24,12) codewords - first=.false. - endif - - itype=-1 - msgsent='*** bad message ***' - itone=0 - i1=index(msg,'>') - if(i1.lt.9) go to 900 - call fmtmsg(msg,iz) - crpt=msg(i1+2:i1+5) - do i=0,7 - if(crpt.eq.rpt(i)) go to 10 - enddo - go to 900 - -10 irpt=i !Report index, 0-7 - if(ichk.lt.10000) then - call hash(msg(2:i1-1),i1-2,ihash) - ihash=iand(ihash,511) !9-bit hash for the two callsigns - ig=8*ihash + irpt !12-bit message information - else - ig=ichk-10000 - endif - ncodeword=ig24(ig) - itone(1:11)=b11 !Insert the Barker-11 code - n=2**24 - do i=12,35 !Insert codeword into itone array - n=n/2 - itone(i)=0 - if(iand(ncodeword,n).ne.0) itone(i)=1 - enddo - msgsent=msg - itype=7 - - n=count(itone(1:35).eq.0) - if(mod(n,2).ne.0) stop 'Parity error in genmsk_short.' - -900 return -end subroutine genmsk_short - -subroutine hash_calls(calls,ih9) - - use hashing - character*(*) calls - i1=index(calls,'>') - call hash(calls(2:i1-1),i1-2,ih9) - ih9=iand(ih9,511) !9-bit hash for the two callsigns - - return -end subroutine hash_calls diff --git a/lib/ldpcsim144.f90 b/lib/ldpcsim144.f90 deleted file mode 100644 index 3121ada2c..000000000 --- a/lib/ldpcsim144.f90 +++ /dev/null @@ -1,175 +0,0 @@ -program ldpcsim - -use, intrinsic :: iso_c_binding -use iso_c_binding, only: c_loc,c_size_t -use hashing -use packjt -parameter(NRECENT=10) -character*12 recent_calls(NRECENT) -character*22 msg,msgsent,msgreceived -character*8 arg -integer*1, allocatable :: codeword(:), decoded(:), message(:) -integer*1, target:: i1Msg8BitBytes(10) -integer*1 i1hash(4) -integer*1 msgbits(80) -integer*4 i4Msg6BitWords(13) -integer ihash -integer nerrtot(0:128),nerrdec(0:128) -real*8, allocatable :: lratio(:), rxdata(:), rxavgd(:) -real, allocatable :: yy(:), llr(:) -equivalence(ihash,i1hash) - -do i=1,NRECENT - recent_calls(i)=' ' -enddo -nerrtot=0 -nerrdec=0 - -nargs=iargc() -if(nargs.ne.4) then - print*,'Usage: ldpcsim niter navg #trials s ' - print*,'eg: ldpcsim 10 1 1000 0.75' - return -endif -call getarg(1,arg) -read(arg,*) max_iterations -call getarg(2,arg) -read(arg,*) navg -call getarg(3,arg) -read(arg,*) ntrials -call getarg(4,arg) -read(arg,*) s - -! don't count hash bits as data bits -K=72 -N=128 -rate=real(K)/real(N) - -write(*,*) "rate: ",rate - -write(*,*) "niter= ",max_iterations," navg= ",navg," s= ",s - -allocate ( codeword(N), decoded(K), message(K) ) -allocate ( lratio(N), rxdata(N), rxavgd(N), yy(N), llr(N) ) - -msg="K9AN K1JT EN50" - call packmsg(msg,i4Msg6BitWords,itype) !Pack into 12 6-bit bytes - call unpackmsg(i4Msg6BitWords,msgsent) !Unpack to get msgsent - write(*,*) "message sent ",msgsent - - i4=0 - ik=0 - im=0 - do i=1,12 - nn=i4Msg6BitWords(i) - do j=1, 6 - ik=ik+1 - i4=i4+i4+iand(1,ishft(nn,j-6)) - i4=iand(i4,255) - if(ik.eq.8) then - im=im+1 -! if(i4.gt.127) i4=i4-256 - i1Msg8BitBytes(im)=i4 - ik=0 - endif - enddo - enddo - - ihash=nhash(c_loc(i1Msg8BitBytes),int(9,c_size_t),146) - ihash=2*iand(ihash,32767) !Generate the 8-bit hash - i1Msg8BitBytes(10)=i1hash(1) !Hash code to byte 10 - mbit=0 - do i=1, 10 - i1=i1Msg8BitBytes(i) - do ibit=1,8 - mbit=mbit+1 - msgbits(mbit)=iand(1,ishft(i1,ibit-8)) - enddo - enddo - call encode_msk144(msgbits,codeword) - call init_random_seed() - -write(*,*) "Eb/N0 SNR2500 ngood nundetected nbadhash sigma" -do idb = 14,-6,-1 - db=idb/2.0-1.0 - sigma=1/sqrt( 2*rate*(10**(db/10.0)) ) - ngood=0 - nue=0 - nbadhash=0 - - do itrial=1, ntrials - rxavgd=0d0 - do iav=1,navg - call sgran() -! Create a realization of a noisy received word - do i=1,N - rxdata(i) = 2.0*codeword(i)-1.0 + sigma*gran() - enddo - rxavgd=rxavgd+rxdata - enddo - rxdata=rxavgd - nerr=0 - do i=1,N - if( rxdata(i)*(2*codeword(i)-1.0) .lt. 0 ) nerr=nerr+1 - enddo - nerrtot(nerr)=nerrtot(nerr)+1 - -! Correct signal normalization is important for this decoder. - rxav=sum(rxdata)/N - rx2av=sum(rxdata*rxdata)/N - rxsig=sqrt(rx2av-rxav*rxav) - rxdata=rxdata/rxsig -! To match the metric to the channel, s should be set to the noise standard deviation. -! For now, set s to the value that optimizes decode probability near threshold. -! The s parameter can be tuned to trade a few tenth's dB of threshold for an order of -! magnitude in UER - if( s .lt. 0 ) then - ss=sigma - else - ss=s - endif - - llr=2.0*rxdata/(ss*ss) - lratio=exp(llr) - yy=rxdata - -! max_iterations is max number of belief propagation iterations -! call ldpc_decode(lratio, decoded, max_iterations, niterations, max_dither, ndither) -! call amsdecode(yy, max_iterations, decoded, niterations) -! call bitflipmsk144(rxdata, decoded, niterations) - call bpdecode144(llr, max_iterations, decoded, niterations) - -! If the decoder finds a valid codeword, niterations will be .ge. 0. - if( niterations .ge. 0 ) then - call extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent) - if( nhashflag .ne. 1 ) then - nbadhash=nbadhash+1 - endif - nueflag=0 - -! Check the message plus hash against what was sent. - do i=1,K - if( msgbits(i) .ne. decoded(i) ) then - nueflag=1 - endif - enddo - if( nhashflag .eq. 1 .and. nueflag .eq. 0 ) then - ngood=ngood+1 - nerrdec(nerr)=nerrdec(nerr)+1 - else if( nhashflag .eq. 1 .and. nueflag .eq. 1 ) then - nue=nue+1; - endif - endif - enddo - snr2500=db-3.5 - write(*,"(f4.1,4x,f5.1,1x,i8,1x,i8,1x,i8,8x,f5.2)") db,snr2500,ngood,nue,nbadhash,ss - -enddo - -open(unit=23,file='nerrhisto.dat',status='unknown') -do i=0,128 - write(23,'(i4,2x,i10,i10,f10.2)') i,nerrdec(i),nerrtot(i),real(nerrdec(i))/real(nerrtot(i)+1e-10) -enddo -close(23) - -end program ldpcsim diff --git a/lib/msk144d.f90 b/lib/msk144d.f90 deleted file mode 100644 index 60c8b0c1d..000000000 --- a/lib/msk144d.f90 +++ /dev/null @@ -1,136 +0,0 @@ -program msk144d - - ! Test the msk144 decoder for WSJT-X - - use options - use timer_module, only: timer - use timer_impl, only: init_timer - use readwav - - character c - character*80 line - character*512 datadir - character*500 infile - character*12 mycall,hiscall - character*6 mygrid - character(len=500) optarg - - logical :: display_help=.false. - logical*1 bShMsgs - logical*1 btrain - logical*1 bswl - - type(wav_header) :: wav - - integer*2 id2(30*12000) - integer*2 ichunk(7*1024) - - real*8 pcoeffs(5) - - type (option) :: long_options(9) = [ & - option ('ndepth',.true.,'c','ndepth',''), & - option ('dxcall',.true.,'d','hiscall',''), & - option ('evemode',.true.,'e','Must be used with -s.',''), & - option ('frequency',.true.,'f','rxfreq',''), & - option ('help',.false.,'h','Display this help message',''), & - option ('mycall',.true.,'m','mycall',''), & - option ('nftol',.true.,'n','nftol',''), & - option ('rxequalize',.false.,'r','Rx Equalize',''), & - option ('short',.false.,'s','enable Sh','') & - ] - t0=0.0 - ndepth=3 - ntol=100 - nrxfreq=1500 - mycall='' - mygrid='EN50WC' - hiscall='' - bShMsgs=.false. - btrain=.false. - bswl=.false. - datadir='.' - pcoeffs=0.d0 - - do - call getopt('c:d:ef:hm:n:rs',long_options,c,optarg,narglen,nstat,noffset,nremain,.true.) - if( nstat .ne. 0 ) then - exit - end if - select case (c) - case ('c') - read (optarg(:narglen), *) ndepth - case ('d') - read (optarg(:narglen), *) hiscall - case ('e') - bswl=.true. - case ('f') - read (optarg(:narglen), *) nrxfreq - case ('h') - display_help = .true. - case ('m') - read (optarg(:narglen), *) mycall - case ('n') - read (optarg(:narglen), *) ntol - case ('r') - btrain=.true. - case ('s') - bShMsgs=.true. - end select - end do - - if(display_help .or. nstat.lt.0 .or. nremain.lt.1) then - print *, '' - print *, 'Usage: msk144d [OPTIONS] file1 [file2 ...]' - print *, '' - print *, ' msk144 decode pre-recorded .WAV file(s)' - print *, '' - print *, 'OPTIONS:' - do i = 1, size (long_options) - call long_options(i) % print (6) - end do - go to 999 - endif - - call init_timer ('timer.out') - call timer('msk144 ',0) - ndecoded=0 - do ifile=noffset+1,noffset+nremain - call get_command_argument(ifile,optarg,narglen) - infile=optarg(:narglen) - call timer('read ',0) - call wav%read (infile) - i1=index(infile,'.wav') - if( i1 .eq. 0 ) i1=index(infile,'.WAV') - read(infile(i1-6:i1-1),*,err=998) nutc - inquire(FILE=infile,SIZE=isize) - npts=min((isize-216)/2,360000) - read(unit=wav%lun) id2(1:npts) - close(unit=wav%lun) - call timer('read ',1) - - do i=1,npts-7*1024+1,7*512 - ichunk=id2(i:i+7*1024-1) - tsec=(i-1)/12000.0 - tt=sum(float(abs(id2(i:i+7*512-1)))) - if( tt .ne. 0.0 ) then - call mskrtd(ichunk,nutc,tsec,ntol,nrxfreq,ndepth,mycall,mygrid,hiscall,bShMsgs, & - btrain,pcoeffs,bswl,datadir,line) - if( index(line,"&") .ne. 0 .or. & - index(line,"^") .ne. 0 .or. & - index(line,"!") .ne. 0 .or. & - index(line,"@") .ne. 0 ) then - write(*,*) line - endif - endif - enddo - enddo - - call timer('msk144 ',1) - call timer('msk144 ',101) - go to 999 - -998 print*,'Cannot read from file:' - print*,infile - -999 continue -end program msk144d diff --git a/lib/msk144sd.f90 b/lib/msk144sd.f90 deleted file mode 100644 index 267fff545..000000000 --- a/lib/msk144sd.f90 +++ /dev/null @@ -1,197 +0,0 @@ -program msk144sd -! -! A simple decoder for slow msk144. -! Can be used as a (slow) brute-force multi-decoder by looping -! over a set of carrier frequencies. -! - use options - use timer_module, only: timer - use timer_impl, only: init_timer - use readwav - - parameter (NRECENT=10) - parameter (NSPM=864) - parameter (NPATTERNS=4) - - character ch - character*80 line - character*500 infile - character*12 mycall,hiscall - character*6 mygrid - character(len=500) optarg - character*22 msgreceived - character*12 recent_calls(NRECENT) - - complex cdat(30*375) - complex c(NSPM) - complex ct(NSPM) - - real softbits(144) - real xmc(NPATTERNS) - - logical :: display_help=.false. - - type(wav_header) :: wav - - integer iavmask(8) - integer iavpatterns(8,NPATTERNS) - integer npkloc(10) - - integer*2 id2(30*12000) - integer*2 ichunk(7*1024) - - data iavpatterns/ & - 1,1,1,1,0,0,0,0, & - 0,1,1,1,1,0,0,0, & - 0,0,1,1,1,1,0,0, & - 1,1,1,1,1,1,0,0/ - data xmc/2.0,4.5,2.5,3.0/ - - type (option) :: long_options(2) = [ & - option ('frequency',.true.,'f','rxfreq',''), & - option ('help',.false.,'h','Display this help message','') & - ] - t0=0.0 - ntol=100 - nrxfreq=1500 - - do - call getopt('f:h',long_options,ch,optarg,narglen,nstat,noffset,nremain,.true.) - if( nstat .ne. 0 ) then - exit - end if - select case (ch) - case ('f') - read (optarg(:narglen), *) nrxfreq - case ('h') - display_help = .true. - end select - end do - - if(display_help .or. nstat.lt.0 .or. nremain.lt.1) then - print *, '' - print *, 'Usage: msk144sd [OPTIONS] file1 [file2 ...]' - print *, '' - print *, ' decode pre-recorded .WAV file(s)' - print *, '' - print *, 'OPTIONS:' - do i = 1, size (long_options) - call long_options(i) % print (6) - end do - go to 999 - endif - - call init_timer ('timer.out') - call timer('msk144 ',0) - ndecoded=0 - do ifile=noffset+1,noffset+nremain - call get_command_argument(ifile,optarg,narglen) - infile=optarg(:narglen) - call timer('read ',0) - call wav%read (infile) - i1=index(infile,'.wav') - if( i1 .eq. 0 ) i1=index(infile,'.WAV') - read(infile(i1-6:i1-1),*,err=998) nutc - inquire(FILE=infile,SIZE=isize) - npts=min((isize-216)/2,360000) - read(unit=wav%lun) id2(1:npts) - close(unit=wav%lun) - call timer('read ',1) - -! do if=1,89 ! brute force multi-decoder - fo=nrxfreq -! fo=(if-1)*25.0+300.0 - call msksddc(id2,npts,fo,cdat) - np=npts/32 - ntol=200 ! actual ntol is ntol/32=6.25 Hz. Detection window is 12.5 Hz wide - fc=1500.0 - call msk144spd(cdat,np,ntol,ndecodesuccess,msgreceived,fc,fest,tdec,navg,ct, & - softbits,recent_calls,nrecent) - nsnr=0 ! need an snr estimate - if( ndecodesuccess .eq. 1 ) then - fest=fo+fest-fc ! fudging because spd thinks input signal is at 1500 Hz - goto 900 - endif -! If short ping decoder doesn't find a decode - npat=NPATTERNS - do iavg=1,npat - iavmask=iavpatterns(1:8,iavg) - navg=sum(iavmask) - deltaf=4.0/real(navg) ! search increment for frequency sync - npeaks=4 - ntol=200 - fc=1500.0 - call msk144sync(cdat(1:6*NSPM),6,ntol,deltaf,iavmask,npeaks,fc, & - fest,npkloc,nsyncsuccess,xmax,c) - if( nsyncsuccess .eq. 0 ) cycle - - do ipk=1,npeaks - do is=1,3 - ic0=npkloc(ipk) - if(is.eq.2) ic0=max(1,ic0-1) - if(is.eq.3) ic0=min(NSPM,ic0+1) - ct=cshift(c,ic0-1) - call msk144decodeframe(ct,softbits,msgreceived,ndecodesuccess, & - recent_calls,nrecent) - if(ndecodesuccess .gt. 0) then - tdec=tsec+xmc(iavg)*tframe - fest=fo+(fest-fc)/32.0 - goto 900 - endif - enddo !Slicer dither - enddo !Peak loop - enddo - -! enddo -900 continue - if( ndecodesuccess .gt. 0 ) then - write(*,1020) nutc,nsnr,tdec,nint(fest),' % ',msgreceived,navg -1020 format(i6.6,i4,f5.1,i5,a3,a22,i4) - endif - enddo - - call timer('msk144 ',1) - call timer('msk144 ',101) - go to 999 - -998 print*,'Cannot read from file:' - print*,infile - -999 continue -end program msk144sd - -subroutine msksddc(id2,npts,fc,cdat) - -! The msk144 detector/demodulator/decoder will decode signals -! with carrier frequency, fc, in the range fN/4 +/- 0.03333*fN. -! -! For slow MSK144 with nslow=32: -! fs=12000/32=375 Hz, fN=187.5 Hz -! -! This routine accepts input samples with fs=12000 Hz. It -! downconverts and decimates by 32 to center a signal with input carrier -! frequency fc at new carrier frequency 1500/32=46.875 Hz. -! The analytic signal is returned. - - parameter (NFFT1=30*12000,NFFT2=30*375) - integer*2 id2(npts) - complex cx(0:NFFT1) - complex cdat(30*375) - - dt=1.0/12000.0 - df=1.0/(NFFT1*dt) - icenter=int(fc/df+0.5) - i46p875=int(46.875/df+0.5) - ishift=icenter-i46p875 - cx=cmplx(0.0,0.0) - cx(1:npts)=id2 - call four2a(cx,NFFT1,1,-1,1) - cx=cshift(cx,ishift) - cx(1)=0.5*cx(1) - cx(2*i46p875+1:)=cmplx(0.0,0.0) - call four2a(cx,NFFT2,1,1,1) - cdat(1:npts/32)=cx(0:npts/32-1)/NFFT1 - return - -end subroutine msksddc - diff --git a/lib/msk144sim.f90 b/lib/msk144sim.f90 index 75c6a1e64..7b2a8a00b 100644 --- a/lib/msk144sim.f90 +++ b/lib/msk144sim.f90 @@ -13,10 +13,10 @@ program msk144sim integer itone(144) !Message bits nargs=iargc() - if(nargs.ne.6) then - print*,'Usage: msk144sim message freq width nslow snr nfiles' - print*,'Example: msk144sim "K1ABC W9XYZ EN37" 1500 0.12 1 2 1' - print*,' msk144sim "K1ABC W9XYZ EN37" 1500 2.5 32 15 1' + if(nargs.ne.5) then + print*,'Usage: msk144sim message freq width snr nfiles' + print*,'Example: msk144sim "K1ABC W9XYZ EN37" 1500 0.12 2 1' + print*,' msk144sim "K1ABC W9XYZ EN37" 1500 2.5 15 1' go to 999 endif call getarg(1,msg) @@ -25,10 +25,8 @@ program msk144sim call getarg(3,arg) read(arg,*) width call getarg(4,arg) - read(arg,*) nslow - call getarg(5,arg) read(arg,*) snrdb - call getarg(6,arg) + call getarg(5,arg) read(arg,*) nfiles !sig is the peak amplitude of the ping. @@ -50,9 +48,9 @@ program msk144sim twopi=8.d0*atan(1.d0) nsym=144 - nsps=6*nslow + nsps=6 if( itone(41) .lt. 0 ) nsym=40 - baud=2000.d0/nslow + baud=2000.d0 dphi0=twopi*(freq-0.25d0*baud)/12000.d0 dphi1=twopi*(freq+0.25d0*baud)/12000.d0 phi=0.0 @@ -79,7 +77,7 @@ program msk144sim go to 999 endif - if(nslow.eq.1) call makepings(pings,NMAX,width,sig) + call makepings(pings,NMAX,width,sig) ! call sgran() do ifile=1,nfiles !Loop over requested number of files @@ -92,8 +90,7 @@ program msk144sim fac=sqrt(6000.0/2500.0) do i=0,NMAX-1 xx=gran() - if(nslow.eq.1) wave(i)=pings(i)*waveform(i) + fac*xx - if(nslow.gt.1) wave(i)=sig*waveform(i) + fac*xx + wave(i)=pings(i)*waveform(i) + fac*xx iwave(i)=30.0*wave(i) enddo diff --git a/lib/platanh.f90 b/lib/platanh.f90 new file mode 100644 index 000000000..e610366d7 --- /dev/null +++ b/lib/platanh.f90 @@ -0,0 +1,24 @@ +subroutine platanh(x,y) + isign=+1 + z=x + if( x.lt.0 ) then + isign=-1 + z=abs(x) + endif + if( z.le. 0.664 ) then + y=x/0.83 + return + elseif( z.le. 0.9217 ) then + y=isign*(z-0.4064)/0.322 + return + elseif( z.le. 0.9951 ) then + y=isign*(z-0.8378)/0.0524 + return + elseif( z.le. 0.9998 ) then + y=isign*(z-0.9914)/0.0012 + return + else + y=isign*7.0 + return + endif +end subroutine platanh diff --git a/lib/pltanh.f90 b/lib/pltanh.f90 new file mode 100644 index 000000000..4c6c2b6d6 --- /dev/null +++ b/lib/pltanh.f90 @@ -0,0 +1,24 @@ +subroutine pltanh(x,y) + isign=+1 + z=x + if( x.lt.0 ) then + isign=-1 + z=abs(x) + endif + if( z.le. 0.8 ) then + y=0.83*x + return + elseif( z.le. 1.6 ) then + y=isign*(0.322*z+0.4064) + return + elseif( z.le. 3.0 ) then + y=isign*(0.0524*z+0.8378) + return + elseif( z.lt. 7.0 ) then + y=isign*(0.0012*z+0.9914) + return + else + y=isign*0.9998 + return + endif +end subroutine pltanh diff --git a/lib/unpackmsg144.f90 b/lib/unpackmsg144.f90 deleted file mode 100644 index 96423ff69..000000000 --- a/lib/unpackmsg144.f90 +++ /dev/null @@ -1,117 +0,0 @@ - subroutine unpackmsg144(dat,msg,c1,c2) -! special unpackmsg for MSK144 - returns call1 and call2 to enable -! maintenance of a recent-calls-heard list - - use packjt - parameter (NBASE=37*36*10*27*27*27) - parameter (NGBASE=180*180) - integer dat(12) - character c1*12,c2*12,grid*4,msg*22,grid6*6,psfx*4,junk2*4 - logical cqnnn - - cqnnn=.false. - nc1=ishft(dat(1),22) + ishft(dat(2),16) + ishft(dat(3),10)+ & - ishft(dat(4),4) + iand(ishft(dat(5),-2),15) - - nc2=ishft(iand(dat(5),3),26) + ishft(dat(6),20) + & - ishft(dat(7),14) + ishft(dat(8),8) + ishft(dat(9),2) + & - iand(ishft(dat(10),-4),3) - - ng=ishft(iand(dat(10),15),12) + ishft(dat(11),6) + dat(12) - - if(ng.ge.32768) then - call unpacktext(nc1,nc2,ng,msg) - c1(1:12)=' ' - c2(1:12)=' ' - go to 100 - endif - - call unpackcall(nc1,c1,iv2,psfx) - if(iv2.eq.0) then - ! This is an "original JT65" message - if(nc1.eq.NBASE+1) c1='CQ ' - if(nc1.eq.NBASE+2) c1='QRZ ' - nfreq=nc1-NBASE-3 - if(nfreq.ge.0 .and. nfreq.le.999) then - write(c1,1002) nfreq - 1002 format('CQ ',i3.3) - cqnnn=.true. - endif - endif - - call unpackcall(nc2,c2,junk1,junk2) - call unpackgrid(ng,grid) - - if(iv2.gt.0) then - ! This is a JT65v2 message - do i=1,4 - if(ichar(psfx(i:i)).eq.0) psfx(i:i)=' ' - enddo - - n1=len_trim(psfx) - n2=len_trim(c2) - if(iv2.eq.1) msg='CQ '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.2) msg='QRZ '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.3) msg='DE '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.4) msg='CQ '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.5) msg='QRZ '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.6) msg='DE '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.7) msg='DE '//c2(:n2)//' '//grid - if(iv2.eq.8) msg=' ' - go to 100 - else - - endif - - grid6=grid//'ma' - call grid2k(grid6,k) - if(k.ge.1 .and. k.le.450) call getpfx2(k,c1) - if(k.ge.451 .and. k.le.900) call getpfx2(k,c2) - - i=index(c1,char(0)) - if(i.ge.3) c1=c1(1:i-1)//' ' - i=index(c2,char(0)) - if(i.ge.3) c2=c2(1:i-1)//' ' - - msg=' ' - j=0 - if(cqnnn) then - msg=c1//' ' - j=7 !### ??? ### - go to 10 - endif - - do i=1,12 - j=j+1 - msg(j:j)=c1(i:i) - if(c1(i:i).eq.' ') go to 10 - enddo - j=j+1 - msg(j:j)=' ' - - 10 do i=1,12 - if(j.le.21) j=j+1 - msg(j:j)=c2(i:i) - if(c2(i:i).eq.' ') go to 20 - enddo - if(j.le.21) j=j+1 - msg(j:j)=' ' - - 20 if(k.eq.0) then - do i=1,4 - if(j.le.21) j=j+1 - msg(j:j)=grid(i:i) - enddo - if(j.le.21) j=j+1 - msg(j:j)=' ' - endif - - 100 continue - if(msg(1:6).eq.'CQ9DX ') msg(3:3)=' ' - if(msg(1:2).eq.'E9' .and. & - msg(3:3).ge.'A' .and. msg(3:3).le.'Z' .and. & - msg(4:4).ge.'A' .and. msg(4:4).le.'Z' .and. & - msg(5:5).eq.' ') msg='CQ '//msg(3:) - - return - end subroutine unpackmsg144 From 59c74905f3527f66d4cb669577be9d03bb324f9e Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Fri, 23 Nov 2018 22:18:43 +0000 Subject: [PATCH 06/13] Force the Aqua theme on macOS to avoid issues with the Mojave dark theme Qt 5.12 is exected to sort out issues with the Mojave dark theme, until then we just have to stop it trying to make WSJT-X dark. --- Darwin/Info.plist.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Darwin/Info.plist.in b/Darwin/Info.plist.in index b62e03682..830975da5 100644 --- a/Darwin/Info.plist.in +++ b/Darwin/Info.plist.in @@ -36,5 +36,7 @@ NSApplication NSHighResolutionCapable True + NSRequiresAquaSystemAppearance + From ef053e5a9fec6f19980e0b0d56f4a5940353ac86 Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Sat, 24 Nov 2018 09:09:50 -0500 Subject: [PATCH 07/13] Protect against execution of on_pbTxMode() when not in "JT9+JT65" mode. --- widgets/mainwindow.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 186f5f40c..26d12a698 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -6482,15 +6482,17 @@ void MainWindow::on_readFreq_clicked() void MainWindow::on_pbTxMode_clicked() { - if(m_modeTx=="JT9") { - m_modeTx="JT65"; - ui->pbTxMode->setText("Tx JT65 #"); - } else { - m_modeTx="JT9"; - ui->pbTxMode->setText("Tx JT9 @"); + if(m_mode=="JT9+JT65") { + if(m_modeTx=="JT9") { + m_modeTx="JT65"; + ui->pbTxMode->setText("Tx JT65 #"); + } else { + m_modeTx="JT9"; + ui->pbTxMode->setText("Tx JT9 @"); + } + m_wideGraph->setModeTx(m_modeTx); + statusChanged(); } - m_wideGraph->setModeTx(m_modeTx); - statusChanged(); } void MainWindow::setXIT(int n, Frequency base) From 7b4b4074554c38ef1a026c90c2715da2c2cba9f0 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Sun, 25 Nov 2018 21:53:38 +0000 Subject: [PATCH 08/13] Improved layout of settings frequencies and station details tables --- Configuration.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Configuration.cpp b/Configuration.cpp index 03f5b2ac2..a75cacc68 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -1091,6 +1091,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network frequencies_.sort (FrequencyList_v2::frequency_column); ui_->frequencies_table_view->setModel (&next_frequencies_); + ui_->frequencies_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->frequencies_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->frequencies_table_view->sortByColumn (FrequencyList_v2::frequency_column, Qt::AscendingOrder); ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true); @@ -1131,6 +1133,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network // stations_.sort (StationList::band_column); ui_->stations_table_view->setModel (&next_stations_); + ui_->stations_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->stations_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->stations_table_view->sortByColumn (StationList::band_column, Qt::AscendingOrder); // stations delegates From 86fb68f305ab41d27a0bd6980897826ea47cae54 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Sun, 25 Nov 2018 21:55:19 +0000 Subject: [PATCH 09/13] Set foreign key item delegate references key values combo box size according to contents --- item_delegates/ForeignKeyDelegate.cpp | 22 +++++++++++++++++++++- item_delegates/ForeignKeyDelegate.hpp | 5 +++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/item_delegates/ForeignKeyDelegate.cpp b/item_delegates/ForeignKeyDelegate.cpp index 09df6a731..204738163 100644 --- a/item_delegates/ForeignKeyDelegate.cpp +++ b/item_delegates/ForeignKeyDelegate.cpp @@ -1,7 +1,10 @@ #include "ForeignKeyDelegate.hpp" +#include #include - +#include +#include +#include #include "CandidateKeyFilter.hpp" ForeignKeyDelegate::ForeignKeyDelegate (QAbstractItemModel const * referenced_model @@ -40,3 +43,20 @@ QWidget * ForeignKeyDelegate::createEditor (QWidget * parent editor->setSizeAdjustPolicy (QComboBox::AdjustToContents); return editor; } + +QSize ForeignKeyDelegate::sizeHint (QStyleOptionViewItem const& option, QModelIndex const& index) const +{ + auto size_hint = QStyledItemDelegate::sizeHint (option, index); + QFontMetrics metrics {option.font}; + QStyleOptionComboBox combo_box_option; + combo_box_option.rect = option.rect; + combo_box_option.state = option.state | QStyle::State_Enabled; + for (auto row = 0; row < candidate_key_filter_->rowCount (); ++row) + { + size_hint = size_hint.expandedTo (qApp->style ()->sizeFromContents (QStyle::CT_ComboBox + , &combo_box_option + , {metrics.width (candidate_key_filter_->data (candidate_key_filter_->index (row, 0)).toString ()) + , metrics.height ()})); + } + return size_hint; +} diff --git a/item_delegates/ForeignKeyDelegate.hpp b/item_delegates/ForeignKeyDelegate.hpp index 01b03ee5b..4f58f4608 100644 --- a/item_delegates/ForeignKeyDelegate.hpp +++ b/item_delegates/ForeignKeyDelegate.hpp @@ -33,9 +33,10 @@ public: , int referencing_key_role = Qt::EditRole); ~ForeignKeyDelegate (); - QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override; - private: + QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override; + QSize sizeHint (QStyleOptionViewItem const&, QModelIndex const&) const override; + QScopedPointer candidate_key_filter_; }; From 314d8a645b740b1adc2358501e7f1e391dbbd08d Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Sun, 25 Nov 2018 22:13:15 +0000 Subject: [PATCH 10/13] Replace deprecated Qt algorithms with C++ Standard Library equivalents --- Configuration.cpp | 8 ++++---- models/FrequencyList.cpp | 14 +++++--------- models/StationList.cpp | 13 ++++--------- widgets/mainwindow.cpp | 5 +++-- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index a75cacc68..bf57206b2 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -2279,10 +2279,10 @@ void Configuration::impl::delete_selected_macros (QModelIndexList selected_rows) { // sort in reverse row order so that we can delete without changing // indices underneath us - qSort (selected_rows.begin (), selected_rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) - { - return rhs.row () < lhs.row (); // reverse row ordering - }); + std::sort (selected_rows.begin (), selected_rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) + { + return rhs.row () < lhs.row (); // reverse row ordering + }); // now delete them Q_FOREACH (auto index, selected_rows) diff --git a/models/FrequencyList.cpp b/models/FrequencyList.cpp index e50afe2a4..55844bdfa 100644 --- a/models/FrequencyList.cpp +++ b/models/FrequencyList.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -350,14 +351,6 @@ bool FrequencyList_v2::remove (Item f) return m_->removeRow (row); } -namespace -{ - bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) - { - return lhs.row () > rhs.row (); - } -} - bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows) { bool result {true}; @@ -371,7 +364,10 @@ bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows) } // reverse sort by row - qSort (rows.begin (), rows.end (), row_is_higher); + std::sort (rows.begin (), rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) + { + return rhs.row () < lhs.row (); // reverse row ordering + }); Q_FOREACH (auto index, rows) { if (result && !m_->removeRow (index.row ())) diff --git a/models/StationList.cpp b/models/StationList.cpp index cfd629ede..eeeb4d607 100644 --- a/models/StationList.cpp +++ b/models/StationList.cpp @@ -139,14 +139,6 @@ bool StationList::remove (Station s) return removeRow (row); } -namespace -{ - bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) - { - return lhs.row () > rhs.row (); - } -} - bool StationList::removeDisjointRows (QModelIndexList rows) { bool result {true}; @@ -160,7 +152,10 @@ bool StationList::removeDisjointRows (QModelIndexList rows) } // reverse sort by row - qSort (rows.begin (), rows.end (), row_is_higher); + std::sort (rows.begin (), rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) + { + return rhs.row () < lhs.row (); // reverse row ordering + }); Q_FOREACH (auto index, rows) { if (result && !m_->removeRow (index.row ())) diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index e0788a8f9..895569942 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -7923,9 +7924,9 @@ QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB) if(isort>1) { if(bReverse) { - qSort(list.begin(),list.end(),qGreater()); + std::sort (list.begin (), list.end (), std::greater ()); } else { - qSort(list.begin(),list.end()); + std::sort (list.begin (), list.end ()); } } From db51726da2a955b49d73ab8f039ec89df66b05e5 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Sun, 25 Nov 2018 22:19:41 +0000 Subject: [PATCH 11/13] Move Fox log reset action to Fox log window context menu and allow deletes of QSOs Move to OnRowChange edit strategy for log tables so that deletes from view can be implemented cleanly. Improve layout of log view tables by resizing to contents. --- models/CabrilloLog.cpp | 17 ++++---- models/CabrilloLog.hpp | 4 +- models/FoxLog.cpp | 17 ++++---- models/FoxLog.hpp | 4 +- qt_db_helpers.hpp | 32 ++++++++++++--- widgets/AbstractLogWindow.cpp | 75 ++++++++++++++++++++++++++++++----- widgets/AbstractLogWindow.hpp | 13 +++++- widgets/CabrilloLogWindow.cpp | 29 +++++++++++--- widgets/CabrilloLogWindow.hpp | 6 ++- widgets/CabrilloLogWindow.ui | 8 +--- widgets/FoxLogWindow.cpp | 46 +++++++++++++++++++-- widgets/FoxLogWindow.hpp | 10 ++++- widgets/FoxLogWindow.ui | 12 +++--- widgets/mainwindow.cpp | 18 ++------- widgets/mainwindow.h | 1 - widgets/mainwindow.ui | 1 - 16 files changed, 214 insertions(+), 79 deletions(-) diff --git a/models/CabrilloLog.cpp b/models/CabrilloLog.cpp index f447bca79..965d6f957 100644 --- a/models/CabrilloLog.cpp +++ b/models/CabrilloLog.cpp @@ -52,7 +52,7 @@ CabrilloLog::impl::impl (Configuration const * configuration) SQL_error_check (export_query_, &QSqlQuery::prepare, "SELECT frequency, \"when\", exchange_sent, call, exchange_rcvd FROM cabrillo_log ORDER BY \"when\""); - setEditStrategy (QSqlTableModel::OnManualSubmit); + setEditStrategy (QSqlTableModel::OnRowChange); setTable ("cabrillo_log"); setHeaderData (fieldIndex ("frequency"), Qt::Horizontal, tr ("Freq(kHz)")); setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)")); @@ -73,7 +73,7 @@ CabrilloLog::~CabrilloLog () { } -QAbstractItemModel * CabrilloLog::model () +QSqlTableModel * CabrilloLog::model () { return &*m_; } @@ -96,7 +96,6 @@ namespace bool CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString const& call , QString const& exchange_sent, QString const& exchange_received) { - ConditionalTransaction transaction {*m_}; auto record = m_->record (); record.setValue ("frequency", frequency / 1000ull); // kHz if (!when.isNull ()) @@ -111,13 +110,12 @@ bool CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString c set_value_maybe_null (record, "exchange_sent", exchange_sent); set_value_maybe_null (record, "exchange_rcvd", exchange_received); set_value_maybe_null (record, "band", m_->configuration_->bands ()->find (frequency)); - SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record); - if (!transaction.submit (false)) + auto ok = m_->insertRecord (-1, record); + if (ok) { - transaction.revert (); - return false; + m_->select (); // to refresh views } - return true; + return ok; } bool CabrilloLog::dupe (Frequency frequency, QString const& call) const @@ -133,9 +131,12 @@ void CabrilloLog::reset () { if (m_->rowCount ()) { + m_->setEditStrategy (QSqlTableModel::OnManualSubmit); ConditionalTransaction transaction {*m_}; SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {}); transaction.submit (); + m_->select (); // to refresh views + m_->setEditStrategy (QSqlTableModel::OnRowChange); } } diff --git a/models/CabrilloLog.hpp b/models/CabrilloLog.hpp index e328d0633..43b9e8f94 100644 --- a/models/CabrilloLog.hpp +++ b/models/CabrilloLog.hpp @@ -8,7 +8,7 @@ class Configuration; class QDateTime; class QString; -class QAbstractItemModel; +class QSqlTableModel; class QTextStream; class CabrilloLog final @@ -25,7 +25,7 @@ public: , QString const& report_sent, QString const& report_received); bool dupe (Frequency, QString const& call) const; - QAbstractItemModel * model (); + QSqlTableModel * model (); void reset (); void export_qsos (QTextStream&) const; diff --git a/models/FoxLog.cpp b/models/FoxLog.cpp index 43cdd2b8f..1503e5662 100644 --- a/models/FoxLog.cpp +++ b/models/FoxLog.cpp @@ -43,7 +43,7 @@ FoxLog::impl::impl () SQL_error_check (dupe_query_, &QSqlQuery::prepare, "SELECT COUNT(*) FROM fox_log WHERE call = :call AND band = :band"); - setEditStrategy (QSqlTableModel::OnManualSubmit); + setEditStrategy (QSqlTableModel::OnRowChange); setTable ("fox_log"); setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)")); setHeaderData (fieldIndex ("call"), Qt::Horizontal, tr ("Call")); @@ -62,7 +62,7 @@ FoxLog::~FoxLog () { } -QAbstractItemModel * FoxLog::model () +QSqlTableModel * FoxLog::model () { return &*m_; } @@ -86,7 +86,6 @@ bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& , QString const& report_sent, QString const& report_received , QString const& band) { - ConditionalTransaction transaction {*m_}; auto record = m_->record (); if (!when.isNull ()) { @@ -101,13 +100,12 @@ bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& set_value_maybe_null (record, "report_sent", report_sent); set_value_maybe_null (record, "report_rcvd", report_received); set_value_maybe_null (record, "band", band); - SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record); - if (!transaction.submit (false)) + auto ok = m_->insertRecord (-1, record); + if (ok) { - transaction.revert (); - return false; + m_->select (); // to refresh views } - return true; + return ok; } bool FoxLog::dupe (QString const& call, QString const& band) const @@ -123,8 +121,11 @@ void FoxLog::reset () { if (m_->rowCount ()) { + m_->setEditStrategy (QSqlTableModel::OnManualSubmit); ConditionalTransaction transaction {*m_}; SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {}); transaction.submit (); + m_->select (); // to refresh views + m_->setEditStrategy (QSqlTableModel::OnRowChange); } } diff --git a/models/FoxLog.hpp b/models/FoxLog.hpp index 028f8205c..caa8e358f 100644 --- a/models/FoxLog.hpp +++ b/models/FoxLog.hpp @@ -6,7 +6,7 @@ class QDateTime; class QString; -class QAbstractItemModel; +class QSqlTableModel; class FoxLog final : private boost::noncopyable @@ -21,7 +21,7 @@ public: , QString const& band); bool dupe (QString const& call, QString const& band) const; - QAbstractItemModel * model (); + QSqlTableModel * model (); void reset (); private: diff --git a/qt_db_helpers.hpp b/qt_db_helpers.hpp index da823b0cb..49a082f72 100644 --- a/qt_db_helpers.hpp +++ b/qt_db_helpers.hpp @@ -30,32 +30,51 @@ public: { model_.database ().transaction (); } + bool submit (bool throw_on_error = true) { - Q_ASSERT (model_.isDirty ()); bool ok {true}; if (throw_on_error) { - SQL_error_check (model_, &QSqlTableModel::submitAll); + SQL_error_check (model_ + , QSqlTableModel::OnManualSubmit == model_.editStrategy () + ? &QSqlTableModel::submitAll + : &QSqlTableModel::submit); } else { - ok = model_.submitAll (); + ok = QSqlTableModel::OnManualSubmit == model_.editStrategy () + ? model_.submitAll () : model_.submit (); } submitted_ = submitted_ || ok; return ok; } + void revert () { - Q_ASSERT (model_.isDirty ()); - model_.revertAll (); + if (QSqlTableModel::OnManualSubmit == model_.editStrategy ()) + { + model_.revertAll (); + } + else + { + model_.revert (); + } } + ~ConditionalTransaction () { if (model_.isDirty ()) { // abandon un-submitted changes to the model - model_.revertAll (); + if (QSqlTableModel::OnManualSubmit == model_.editStrategy ()) + { + model_.revertAll (); + } + else + { + model_.revert (); + } } auto database = model_.database (); if (submitted_) @@ -67,6 +86,7 @@ public: database.rollback (); } } + private: QSqlTableModel& model_; bool submitted_; diff --git a/widgets/AbstractLogWindow.cpp b/widgets/AbstractLogWindow.cpp index ebc901cd8..e796cdc3e 100644 --- a/widgets/AbstractLogWindow.cpp +++ b/widgets/AbstractLogWindow.cpp @@ -1,25 +1,36 @@ #include "AbstractLogWindow.hpp" +#include #include #include #include #include +#include +#include +#include +#include #include "Configuration.hpp" #include "SettingsGroup.hpp" +#include "MessageBox.hpp" #include "models/FontOverrideModel.hpp" #include "pimpl_impl.hpp" class AbstractLogWindow::impl final { public: - impl (QString const& settings_key, QSettings * settings, Configuration const * configuration) - : settings_key_ {settings_key} + impl (AbstractLogWindow * self, QString const& settings_key, QSettings * settings + , Configuration const * configuration) + : self_ {self} + , settings_key_ {settings_key} , settings_ {settings} , configuration_ {configuration} , log_view_ {nullptr} { } + void delete_QSOs (); + + AbstractLogWindow * self_; QString settings_key_; QSettings * settings_; Configuration const * configuration_; @@ -27,11 +38,51 @@ public: FontOverrideModel model_; }; +namespace +{ + bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) + { + return lhs.row () > rhs.row (); + } +} + +void AbstractLogWindow::impl::delete_QSOs () +{ + auto selection_model = log_view_->selectionModel (); + selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + auto row_indexes = selection_model->selectedRows (); + + if (row_indexes.size () + && MessageBox::Yes == MessageBox::query_message (self_ + , tr ("Confirm Delete") + , tr ("Are you sure you want to delete the %n " + "selected QSO(s) from the log", "" + , row_indexes.size ()))) + { + // We must work with source model indexes because we don't want row + // removes to invalidate model indexes we haven't yet processed. We + // achieve that by processing them in decending row order. + for (auto& row_index : row_indexes) + { + row_index = model_.mapToSource (row_index); + } + + // reverse sort by row + std::sort (row_indexes.begin (), row_indexes.end (), row_is_higher); + for (auto index : row_indexes) + { + auto row = model_.mapFromSource (index).row (); + model_.removeRow (row); + self_->log_model_changed (); + } + } +} + AbstractLogWindow::AbstractLogWindow (QString const& settings_key, QSettings * settings , Configuration const * configuration , QWidget * parent) : QWidget {parent} - , m_ {settings_key, settings, configuration} + , m_ {this, settings_key, settings, configuration} { // ensure view scrolls to latest new row connect (&m_->model_, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const& /*parent*/, int /*first*/, int /*last*/) { @@ -45,18 +96,17 @@ AbstractLogWindow::~AbstractLogWindow () m_->settings_->setValue ("window/geometry", saveGeometry ()); } -void AbstractLogWindow::set_log_model (QAbstractItemModel * log_model) -{ - m_->model_.setSourceModel (log_model); -} - void AbstractLogWindow::set_log_view (QTableView * log_view) { // do this here because we know the UI must be setup before this SettingsGroup g {m_->settings_, m_->settings_key_}; restoreGeometry (m_->settings_->value ("window/geometry").toByteArray ()); - m_->log_view_ = log_view; + m_->log_view_->setContextMenuPolicy (Qt::ActionsContextMenu); + m_->log_view_->setAlternatingRowColors (true); + m_->log_view_->setSelectionBehavior (QAbstractItemView::SelectRows); + m_->log_view_->setSelectionMode (QAbstractItemView::ExtendedSelection); + m_->model_.setSourceModel (m_->log_view_->model ()); m_->log_view_->setModel (&m_->model_); m_->log_view_->setColumnHidden (0, true); auto horizontal_header = log_view->horizontalHeader (); @@ -65,6 +115,13 @@ void AbstractLogWindow::set_log_view (QTableView * log_view) m_->log_view_->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); set_log_view_font (m_->configuration_->decoded_text_font ()); m_->log_view_->scrollToBottom (); + + // actions + auto delete_action = new QAction {tr ("&Delete ..."), m_->log_view_}; + m_->log_view_->insertAction (nullptr, delete_action); + connect (delete_action, &QAction::triggered, [this] (bool /*checked*/) { + m_->delete_QSOs (); + }); } void AbstractLogWindow::set_log_view_font (QFont const& font) diff --git a/widgets/AbstractLogWindow.hpp b/widgets/AbstractLogWindow.hpp index 35d5d27cf..581212d82 100644 --- a/widgets/AbstractLogWindow.hpp +++ b/widgets/AbstractLogWindow.hpp @@ -7,10 +7,15 @@ class QString; class QSettings; class Configuration; -class QAbstractItemModel; class QTableView; class QFont; +// +// AbstractLogWindow - Base class for log view windows +// +// QWidget that manages the common functionality shared by windows +// that include a QSO log view. +// class AbstractLogWindow : public QWidget { @@ -20,11 +25,15 @@ public: , QWidget * parent = nullptr); virtual ~AbstractLogWindow () = 0; - void set_log_model (QAbstractItemModel *); + // set the QTableView that shows the log records, must have its + // model set before calling this void set_log_view (QTableView *); + void set_log_view_font (QFont const&); private: + virtual void log_model_changed (int row = -1) = 0; + class impl; pimpl m_; }; diff --git a/widgets/CabrilloLogWindow.cpp b/widgets/CabrilloLogWindow.cpp index ab83a79f2..20f39e4e6 100644 --- a/widgets/CabrilloLogWindow.cpp +++ b/widgets/CabrilloLogWindow.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "Configuration.hpp" #include "models/Bands.hpp" #include "item_delegates/ForeignKeyDelegate.hpp" @@ -43,26 +44,44 @@ namespace class CabrilloLogWindow::impl final { public: - explicit impl () = default; + explicit impl (QSqlTableModel * log_model) + : log_model_ {log_model} + { + } + + QSqlTableModel * log_model_; FormatProxyModel format_model_; Ui::CabrilloLogWindow ui_; }; CabrilloLogWindow::CabrilloLogWindow (QSettings * settings, Configuration const * configuration - , QAbstractItemModel * cabrillo_log_model, QWidget * parent) + , QSqlTableModel * cabrillo_log_model, QWidget * parent) : AbstractLogWindow {"Cabrillo Log Window", settings, configuration, parent} + , m_{cabrillo_log_model} { setWindowTitle (QApplication::applicationName () + " - Cabrillo Log"); m_->ui_.setupUi (this); - m_->format_model_.setSourceModel (cabrillo_log_model); - set_log_model (&m_->format_model_); + m_->format_model_.setSourceModel (m_->log_model_); + m_->ui_.log_table_view->setModel (&m_->format_model_); set_log_view (m_->ui_.log_table_view); m_->ui_.log_table_view->setItemDelegateForColumn (2, new DateTimeAsSecsSinceEpochDelegate {this}); m_->ui_.log_table_view->setItemDelegateForColumn (3, new CallsignDelegate {this}); - m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), cabrillo_log_model, 0, 6, this}); + m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), m_->log_model_, 0, 6, this}); m_->ui_.log_table_view->horizontalHeader ()->moveSection (6, 1); // band to first column } CabrilloLogWindow::~CabrilloLogWindow () { } + +void CabrilloLogWindow::log_model_changed (int row) +{ + if (row >= 0) + { + m_->log_model_->selectRow (row); + } + else + { + m_->log_model_->select (); + } +} diff --git a/widgets/CabrilloLogWindow.hpp b/widgets/CabrilloLogWindow.hpp index c0a964ce5..e2aa1627a 100644 --- a/widgets/CabrilloLogWindow.hpp +++ b/widgets/CabrilloLogWindow.hpp @@ -7,17 +7,19 @@ class QSettings; class Configuration; class QFont; -class QAbstractItemModel; +class QSqlTableModel; class CabrilloLogWindow final : public AbstractLogWindow { public: - explicit CabrilloLogWindow (QSettings *, Configuration const *, QAbstractItemModel * cabrillo_log_model + explicit CabrilloLogWindow (QSettings *, Configuration const *, QSqlTableModel * cabrillo_log_model , QWidget * parent = nullptr); ~CabrilloLogWindow (); private: + void log_model_changed (int row) override; + class impl; pimpl m_; }; diff --git a/widgets/CabrilloLogWindow.ui b/widgets/CabrilloLogWindow.ui index 43061f132..929b41a7b 100644 --- a/widgets/CabrilloLogWindow.ui +++ b/widgets/CabrilloLogWindow.ui @@ -6,7 +6,7 @@ 0 0 - 274 + 493 210 @@ -16,12 +16,6 @@ - - true - - - QAbstractItemView::SingleSelection - true diff --git a/widgets/FoxLogWindow.cpp b/widgets/FoxLogWindow.cpp index f5e7e517d..21029c73c 100644 --- a/widgets/FoxLogWindow.cpp +++ b/widgets/FoxLogWindow.cpp @@ -1,9 +1,14 @@ #include "FoxLogWindow.hpp" #include +#include +#include +#include +#include #include "SettingsGroup.hpp" #include "Configuration.hpp" +#include "MessageBox.hpp" #include "models/Bands.hpp" #include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp" @@ -16,26 +21,47 @@ class FoxLogWindow::impl final { public: - explicit impl () = default; + explicit impl (QSqlTableModel * log_model) + : log_model_ {log_model} + { + } + + QSqlTableModel * log_model_; Ui::FoxLogWindow ui_; }; FoxLogWindow::FoxLogWindow (QSettings * settings, Configuration const * configuration - , QAbstractItemModel * fox_log_model, QWidget * parent) + , QSqlTableModel * fox_log_model, QWidget * parent) : AbstractLogWindow {"Fox Log Window", settings, configuration, parent} + , m_ {fox_log_model} { setWindowTitle (QApplication::applicationName () + " - Fox Log"); m_->ui_.setupUi (this); - set_log_model (fox_log_model); + m_->ui_.log_table_view->setModel (m_->log_model_); set_log_view (m_->ui_.log_table_view); m_->ui_.log_table_view->setItemDelegateForColumn (1, new DateTimeAsSecsSinceEpochDelegate {this}); m_->ui_.log_table_view->setItemDelegateForColumn (2, new CallsignDelegate {this}); m_->ui_.log_table_view->setItemDelegateForColumn (3, new MaidenheadLocatorDelegate {this}); - m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), fox_log_model, 0, 6, this}); + m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), m_->log_model_, 0, 6, this}); m_->ui_.log_table_view->horizontalHeader ()->moveSection (6, 1); // move band to first column m_->ui_.rate_label->setNum (0); m_->ui_.queued_label->setNum (0); m_->ui_.callers_label->setNum (0); + + // actions + auto reset_action = new QAction {tr ("&Reset ..."), m_->ui_.log_table_view}; + m_->ui_.log_table_view->insertAction (nullptr, reset_action); + connect (reset_action, &QAction::triggered, [this, configuration] (bool /*checked*/) { + if (MessageBox::Yes == MessageBox::query_message( this + , tr ("Confirm Reset") + , tr ("Are you sure you want to erase file FoxQSO.txt " + "and start a new Fox log?"))) + { + QFile f{configuration->writeable_data_dir ().absoluteFilePath ("FoxQSO.txt")}; + f.remove (); + Q_EMIT reset_log_model (); + } + }); } FoxLogWindow::~FoxLogWindow () @@ -56,3 +82,15 @@ void FoxLogWindow::rate (int n) { m_->ui_.rate_label->setNum (n); } + +void FoxLogWindow::log_model_changed (int row) +{ + if (row >= 0) + { + m_->log_model_->selectRow (row); + } + else + { + m_->log_model_->select (); + } +} diff --git a/widgets/FoxLogWindow.hpp b/widgets/FoxLogWindow.hpp index d27ccdea6..65bcb0bd0 100644 --- a/widgets/FoxLogWindow.hpp +++ b/widgets/FoxLogWindow.hpp @@ -7,13 +7,15 @@ class QSettings; class Configuration; class QFont; -class QAbstractItemModel; +class QSqlTableModel; class FoxLogWindow final : public AbstractLogWindow { + Q_OBJECT + public: - explicit FoxLogWindow (QSettings *, Configuration const *, QAbstractItemModel * fox_log_model + explicit FoxLogWindow (QSettings *, Configuration const *, QSqlTableModel * fox_log_model , QWidget * parent = nullptr); ~FoxLogWindow (); @@ -21,7 +23,11 @@ public: void queued (int); void rate (int); + Q_SIGNAL void reset_log_model () const; + private: + void log_model_changed (int row) override; + class impl; pimpl m_; }; diff --git a/widgets/FoxLogWindow.ui b/widgets/FoxLogWindow.ui index 96ef8b112..0a9815478 100644 --- a/widgets/FoxLogWindow.ui +++ b/widgets/FoxLogWindow.ui @@ -6,21 +6,21 @@ 0 0 - 331 + 453 238 + + Qt::DefaultContextMenu + Fox Log - - true - - - QAbstractItemView::SingleSelection + + Qt::ActionsContextMenu true diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 895569942..18f6cee84 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -2419,6 +2419,10 @@ void MainWindow::on_fox_log_action_triggered() // Connect signals from fox log window connect (this, &MainWindow::finished, m_foxLogWindow.data (), &FoxLogWindow::close); + connect (m_foxLogWindow.data (), &FoxLogWindow::reset_log_model, [this] () { + if (!m_foxLog) m_foxLog.reset (new FoxLog); + m_foxLog->reset (); + }); } m_foxLogWindow->showNormal (); m_foxLogWindow->raise (); @@ -5332,7 +5336,6 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button default: break; } - using SpOp = Configuration::SpecialOperatingActivity; auto special_op = m_config.special_op_id (); if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) { @@ -5377,7 +5380,6 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, if (m_config.clear_DX () and SpecOp::HOUND != m_config.special_op_id()) clearDX (); m_dateTimeQSOOn = QDateTime {}; - using SpOp = Configuration::SpecialOperatingActivity; auto special_op = m_config.special_op_id (); if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) { @@ -6119,18 +6121,6 @@ void MainWindow::on_actionErase_ALL_TXT_triggered() //Erase ALL.TXT } } -void MainWindow::on_reset_fox_log_action_triggered () -{ - int ret = MessageBox::query_message(this, tr("Confirm Reset"), - tr("Are you sure you want to erase file FoxQSO.txt and start a new Fox log?")); - if(ret==MessageBox::Yes) { - QFile f{m_config.writeable_data_dir().absoluteFilePath("FoxQSO.txt")}; - f.remove(); - if (!m_foxLog) m_foxLog.reset (new FoxLog); - m_foxLog->reset (); - } -} - void MainWindow::on_reset_cabrillo_log_action_triggered () { if (MessageBox::Yes == MessageBox::query_message (this, tr ("Confirm Reset"), diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index e5b9de9a8..4cab787b5 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -204,7 +204,6 @@ private slots: void on_actionDeepestDecode_toggled (bool); void bumpFqso(int n); void on_actionErase_ALL_TXT_triggered(); - void on_reset_fox_log_action_triggered (); void on_reset_cabrillo_log_action_triggered (); void on_actionErase_wsjtx_log_adi_triggered(); void on_actionExport_Cabrillo_log_triggered(); diff --git a/widgets/mainwindow.ui b/widgets/mainwindow.ui index 8256eb606..21799434e 100644 --- a/widgets/mainwindow.ui +++ b/widgets/mainwindow.ui @@ -2653,7 +2653,6 @@ list. The list can be maintained in Settings (F2). - From 4334c997ee7526115733963323f3a27b6f4c87ea Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Sun, 25 Nov 2018 22:30:28 +0000 Subject: [PATCH 12/13] Add tool tips to log view windows --- widgets/CabrilloLogWindow.ui | 3 +++ widgets/FoxLogWindow.ui | 3 +++ 2 files changed, 6 insertions(+) diff --git a/widgets/CabrilloLogWindow.ui b/widgets/CabrilloLogWindow.ui index 929b41a7b..efefe5d3c 100644 --- a/widgets/CabrilloLogWindow.ui +++ b/widgets/CabrilloLogWindow.ui @@ -16,6 +16,9 @@ + + <html><head/><body><p>Right-click here for available actions.</p></body></html> + true diff --git a/widgets/FoxLogWindow.ui b/widgets/FoxLogWindow.ui index 0a9815478..cd224ed33 100644 --- a/widgets/FoxLogWindow.ui +++ b/widgets/FoxLogWindow.ui @@ -22,6 +22,9 @@ Qt::ActionsContextMenu + + <html><head/><body><p>Right-click here for available actions.</p></body></html> + true From 4dbba727ecaefd63439ec5b1a435134519bb8769 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Mon, 26 Nov 2018 01:42:57 +0000 Subject: [PATCH 13/13] Explicitly include MOC generated source --- widgets/FoxLogWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/widgets/FoxLogWindow.cpp b/widgets/FoxLogWindow.cpp index 21029c73c..0f96529aa 100644 --- a/widgets/FoxLogWindow.cpp +++ b/widgets/FoxLogWindow.cpp @@ -17,6 +17,7 @@ #include "pimpl_impl.hpp" #include "ui_FoxLogWindow.h" +#include "moc_FoxLogWindow.cpp" class FoxLogWindow::impl final {