diff --git a/Audio/soundin.cpp b/Audio/soundin.cpp index 81d323d54..ecab8e052 100644 --- a/Audio/soundin.cpp +++ b/Audio/soundin.cpp @@ -180,13 +180,15 @@ void SoundInput::reset (bool report_dropped_frames) if (cummulative_lost_usec_ != std::numeric_limits::min () && report_dropped_frames) { auto lost_usec = elapsed_usecs - m_stream->processedUSecs () - cummulative_lost_usec_; - if (std::abs (lost_usec) > 48000 / 5) - { - LOG_WARN ("Detected dropped audio source samples: " - << m_stream->format ().framesForDuration (lost_usec) - << " (" << std::setprecision (4) << lost_usec / 1.e6 << " S)"); - } - else if (std::abs (lost_usec) > 5 * 48000) + // disable log warnings on dropped audio for now, as detection is not reliable +// if (std::abs (lost_usec) > 48000 / 5) +// { +// LOG_WARN ("Detected dropped audio source samples: " +// << m_stream->format ().framesForDuration (lost_usec) +// << " (" << std::setprecision (4) << lost_usec / 1.e6 << " S)"); +// } +// else if (std::abs (lost_usec) > 5 * 48000) + if (std::abs (lost_usec) > 5 * 48000) { LOG_ERROR ("Detected excessive dropped audio source samples: " << m_stream->format ().framesForDuration (lost_usec) diff --git a/CMakeLists.txt b/CMakeLists.txt index 66f5f127f..c47d51eca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ set (wsjtx_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_ set (PROJECT_BUNDLE_NAME "WSJT-X") set (PROJECT_VENDOR "Joe Taylor, K1JT") set (PROJECT_CONTACT "Joe Taylor ") -set (PROJECT_COPYRIGHT "Copyright (C) 2001-2021 by Joe Taylor, K1JT") +set (PROJECT_COPYRIGHT "Copyright (C) 2001-2022 by Joe Taylor, K1JT") set (PROJECT_HOMEPAGE https://www.physics.princeton.edu/pulsar/K1JT/wsjtx.html) set (PROJECT_MANUAL wsjtx-main) set (PROJECT_MANUAL_DIRECTORY_URL https://www.physics.princeton.edu/pulsar/K1JT/wsjtx-doc/) @@ -190,6 +190,7 @@ set (wsjt_qt_CXXSRCS widgets/FrequencyDeltaLineEdit.cpp item_delegates/CandidateKeyFilter.cpp item_delegates/ForeignKeyDelegate.cpp + item_delegates/MessageItemDelegate.cpp validators/LiveFrequencyValidator.cpp GetUserId.cpp Audio/AudioDevice.cpp diff --git a/Configuration.cpp b/Configuration.cpp index 565b73b2e..cc977fd7c 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -168,6 +168,11 @@ #include #include #include +#include +#include +#include +#include + #include "pimpl_impl.hpp" #include "Logger.hpp" @@ -180,6 +185,7 @@ #include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/FrequencyDelegate.hpp" #include "item_delegates/FrequencyDeltaDelegate.hpp" +#include "item_delegates/MessageItemDelegate.hpp" #include "Transceiver/TransceiverFactory.hpp" #include "Transceiver/Transceiver.hpp" #include "models/Bands.hpp" @@ -250,7 +256,8 @@ namespace // Magic numbers for file validation constexpr quint32 qrg_magic {0xadbccbdb}; - constexpr quint32 qrg_version {100}; // M.mm + constexpr quint32 qrg_version {101}; // M.mm + constexpr quint32 qrg_version_100 {100}; } @@ -263,13 +270,27 @@ class FrequencyDialog final Q_OBJECT public: - using Item = FrequencyList_v2::Item; + using Item = FrequencyList_v2_101::Item; explicit FrequencyDialog (IARURegions * regions_model, Modes * modes_model, QWidget * parent = nullptr) : QDialog {parent} { + start_date_time_edit_ = new QDateTimeEdit(QDateTime(QDate::currentDate(), QTime(0,0,0,0), Qt::UTC), parent); + end_date_time_edit_ = new QDateTimeEdit(QDateTime(QDate::currentDate().addDays(2), QTime(0,0,0,0), Qt::UTC), parent); + + enable_dates_checkbox_ = new QCheckBox {tr ("")}; + start_date_time_edit_->setDisplayFormat("yyyy.MM.dd hh:mm:ss 'UTC'"); + start_date_time_edit_->setTimeSpec(Qt::UTC); + start_date_time_edit_->setMinimumDate(QDate::currentDate().addDays(-365)); + + end_date_time_edit_->setDisplayFormat("yyyy.MM.dd hh:mm:ss 'UTC'"); + end_date_time_edit_->setTimeSpec(Qt::UTC); + end_date_time_edit_->setMinimumDate(QDate::currentDate().addDays(-365)); + preferred_frequency_checkbox_ = new QCheckBox {tr ("")}; + setWindowTitle (QApplication::applicationName () + " - " + tr ("Add Frequency")); + region_combo_box_.setModel (regions_model); mode_combo_box_.setModel (modes_model); @@ -277,28 +298,74 @@ public: form_layout->addRow (tr ("IARU &Region:"), ®ion_combo_box_); form_layout->addRow (tr ("&Mode:"), &mode_combo_box_); form_layout->addRow (tr ("&Frequency (MHz):"), &frequency_line_edit_); + form_layout->addRow (tr ("&Preferred for Band/Mode:"), preferred_frequency_checkbox_); + form_layout->addRow (tr ("&Description:"), &description_line_edit_); + form_layout->addRow (tr ("&Enable Date Range:"), enable_dates_checkbox_); + form_layout->addRow (tr ("S&tart:"), start_date_time_edit_); + form_layout->addRow (tr ("&End:"), end_date_time_edit_); + form_layout->addRow (tr ("&Source:"), &source_line_edit_); auto main_layout = new QVBoxLayout (this); main_layout->addLayout (form_layout); - auto button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel}; + button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel}; main_layout->addWidget (button_box); connect (button_box, &QDialogButtonBox::accepted, this, &FrequencyDialog::accept); connect (button_box, &QDialogButtonBox::rejected, this, &FrequencyDialog::reject); + connect(start_date_time_edit_, &QDateTimeEdit::dateTimeChanged, this, &FrequencyDialog::checkSaneDates); + connect(end_date_time_edit_, &QDateTimeEdit::dateTimeChanged, this, &FrequencyDialog::checkSaneDates); + connect(enable_dates_checkbox_, &QCheckBox::stateChanged, this, &FrequencyDialog::toggleValidity); + toggleValidity(); } + void toggleValidity() + { + start_date_time_edit_->setEnabled(enable_dates_checkbox_->isChecked()); + end_date_time_edit_->setEnabled(enable_dates_checkbox_->isChecked()); + checkSaneDates(); + } + + void checkSaneDates() + { + if (enable_dates_checkbox_->isChecked() && start_date_time_edit_->dateTime().isValid() && end_date_time_edit_->dateTime().isValid()) + { + if (start_date_time_edit_->dateTime() > end_date_time_edit_->dateTime()) + { + QMessageBox::warning(this, tr("Invalid Date Range"), tr("Start date must be before end date")); + button_box->button(QDialogButtonBox::Ok)->setEnabled(false); + return; + } + } + button_box->button(QDialogButtonBox::Ok)->setEnabled(true); + } + Item item () const { - return {frequency_line_edit_.frequency () - , Modes::value (mode_combo_box_.currentText ()) - , IARURegions::value (region_combo_box_.currentText ())}; + QDateTime start_time = enable_dates_checkbox_->isChecked() ? start_date_time_edit_->dateTime() : QDateTime(); + QDateTime end_time = enable_dates_checkbox_->isChecked() ? end_date_time_edit_->dateTime() : QDateTime(); + return { + frequency_line_edit_.frequency(), + Modes::value(mode_combo_box_.currentText()), + IARURegions::value(region_combo_box_.currentText()), + description_line_edit_.text(), source_line_edit_.text(), + start_time, + end_time, + preferred_frequency_checkbox_->isChecked() + }; } private: QComboBox region_combo_box_; QComboBox mode_combo_box_; FrequencyLineEdit frequency_line_edit_; + QLineEdit description_line_edit_; + QLineEdit source_line_edit_; + QDialogButtonBox * button_box; + QCheckBox *enable_dates_checkbox_; + QCheckBox *preferred_frequency_checkbox_; + QDateTimeEdit *end_date_time_edit_; + QDateTimeEdit *start_date_time_edit_; }; @@ -375,31 +442,7 @@ public: }; -// -// Class MessageItemDelegate -// -// Item delegate for message entry such as free text message macros. -// -class MessageItemDelegate final - : public QStyledItemDelegate -{ -public: - explicit MessageItemDelegate (QObject * parent = nullptr) - : QStyledItemDelegate {parent} - { - } - QWidget * createEditor (QWidget * parent - , QStyleOptionViewItem const& /* option*/ - , QModelIndex const& /* index */ - ) const override - { - auto editor = new QLineEdit {parent}; - editor->setFrame (false); - editor->setValidator (new QRegularExpressionValidator {message_alphabet, editor}); - return editor; - } -}; // Internal implementation of the Configuration class. class Configuration::impl final @@ -477,7 +520,9 @@ private: void save_frequencies (); void reset_frequencies (); void insert_frequency (); - FrequencyList_v2::FrequencyItems read_frequencies_file (QString const&); + void size_frequency_table_columns(); + + FrequencyList_v2_101::FrequencyItems read_frequencies_file (QString const&); void delete_stations (); void insert_station (); @@ -569,8 +614,8 @@ private: IARURegions regions_; IARURegions::Region region_; Modes modes_; - FrequencyList_v2 frequencies_; - FrequencyList_v2 next_frequencies_; + FrequencyList_v2_101 frequencies_; + FrequencyList_v2_101 next_frequencies_; StationList stations_; StationList next_stations_; FrequencyDelta current_offset_; @@ -776,8 +821,8 @@ Bands const * Configuration::bands () const {return &m_->bands_;} StationList * Configuration::stations () {return &m_->stations_;} StationList const * Configuration::stations () const {return &m_->stations_;} IARURegions::Region Configuration::region () const {return m_->region_;} -FrequencyList_v2 * Configuration::frequencies () {return &m_->frequencies_;} -FrequencyList_v2 const * Configuration::frequencies () const {return &m_->frequencies_;} +FrequencyList_v2_101 * Configuration::frequencies () {return &m_->frequencies_;} +FrequencyList_v2_101 const * Configuration::frequencies () const {return &m_->frequencies_;} QStringListModel * Configuration::macros () {return &m_->macros_;} QStringListModel const * Configuration::macros () const {return &m_->macros_;} QDir Configuration::save_directory () const {return m_->save_directory_;} @@ -1045,7 +1090,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network throw std::runtime_error {"Failed to create save directory"}; } - // we now have a deafult save path that exists + // we now have a default save path that exists // make sure samples directory exists QString samples_dir {"samples"}; @@ -1194,20 +1239,23 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network // // setup working frequencies table model & view // - frequencies_.sort (FrequencyList_v2::frequency_column); + frequencies_.sort (FrequencyList_v2_101::frequency_column); ui_->frequencies_table_view->setModel (&next_frequencies_); ui_->frequencies_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->frequencies_table_view->horizontalHeader ()->setResizeContentsPrecision (0); + ui_->frequencies_table_view->horizontalHeader ()->moveSection(8, 3); // swap preferred to be in front of description ui_->frequencies_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->frequencies_table_view->verticalHeader ()->setResizeContentsPrecision (0); - ui_->frequencies_table_view->sortByColumn (FrequencyList_v2::frequency_column, Qt::AscendingOrder); - ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true); + ui_->frequencies_table_view->sortByColumn (FrequencyList_v2_101::frequency_column, Qt::AscendingOrder); + ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2_101::frequency_mhz_column, true); + ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2_101::source_column, true); // delegates - ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::frequency_column, new FrequencyDelegate {this}); - ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::region_column, new ForeignKeyDelegate {®ions_, 0, this}); - ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::mode_column, new ForeignKeyDelegate {&modes_, 0, this}); + ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2_101::frequency_column, new FrequencyDelegate {this}); + ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2_101::region_column, new ForeignKeyDelegate {®ions_, 0, this}); + ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2_101::mode_column, new ForeignKeyDelegate {&modes_, 0, this}); // actions frequency_delete_action_ = new QAction {tr ("&Delete"), ui_->frequencies_table_view}; @@ -1452,6 +1500,13 @@ void Configuration::impl::done (int r) void Configuration::impl::read_settings () { SettingsGroup g {settings_, "Configuration"}; + LOG_INFO(QString{"Configuration Settings (%1)"}.arg(settings_->fileName())); + QStringList keys = settings_->allKeys(); + //Q_FOREACH (auto const& item, keys) + //{ + // LOG_INFO(QString{" %1 = %2"}.arg(item).arg(settings_->value(item).toString())); + //} + restoreGeometry (settings_->value ("window/geometry").toByteArray ()); my_callsign_ = settings_->value ("MyCall", QString {}).toString (); @@ -1505,12 +1560,20 @@ void Configuration::impl::read_settings () region_ = settings_->value ("Region", QVariant::fromValue (IARURegions::ALL)).value (); - if (settings_->contains ("FrequenciesForRegionModes")) + LOG_INFO(QString{"Reading frequencies"}); + + if (settings_->contains ("FrequenciesForRegionModes_v2")) { - auto const& v = settings_->value ("FrequenciesForRegionModes"); + LOG_INFO(QString{"read_settings found FrequenciesForRegionModes_v2"}); + if (settings_->contains ("FrequenciesForRegionModes")) + { + LOG_INFO(QString{"read_settings ALSO found FrequenciesForRegionModes"}); + } + + auto const& v = settings_->value ("FrequenciesForRegionModes_v2"); if (v.isValid ()) { - frequencies_.frequency_list (v.value ()); + frequencies_.frequency_list (v.value ()); } else { @@ -1519,7 +1582,34 @@ void Configuration::impl::read_settings () } else { - frequencies_.reset_to_defaults (); + LOG_INFO(QString{"read_settings looking for FrequenciesForRegionModes"}); + if (settings_->contains ("FrequenciesForRegionModes")) // has the old ones. + { + LOG_INFO(QString{"found FrequenciesForRegionModes"}); + auto const& v = settings_->value("FrequenciesForRegionModes"); + LOG_INFO(QString{"v is %1"}.arg(v.typeName())); + if (v.isValid()) + { + LOG_INFO(QString{"read_settings found VALID FrequenciesForRegionModes"}); + FrequencyList_v2_101::FrequencyItems list; + FrequencyList_v2::FrequencyItems v100 = v.value(); + LOG_INFO(QString{"read_settings read %1 old_format items from FrequenciesForRegionModes"}.arg(v100.size())); + + Q_FOREACH (auto const& item, v100) + { + list << FrequencyList_v2_101::Item{item.frequency_, item.mode_, item.region_, QString(), QString(), QDateTime(), + QDateTime(), false}; + } + LOG_INFO(QString{"converted %1 items to FrequenciesForRegionModes_v2"}.arg(list.size())); + + frequencies_.frequency_list(list); + } + else + { + LOG_INFO(QString{"read_settings INVALID FrequenciesForRegionModes"}); + frequencies_.reset_to_defaults(); + } + } } stations_.station_list (settings_->value ("stations").value ()); @@ -1661,8 +1751,8 @@ void Configuration::impl::write_settings () settings_->setValue ("After73", id_after_73_); settings_->setValue ("TxQSYAllowed", tx_QSY_allowed_); settings_->setValue ("Macros", macros_.stringList ()); - settings_->setValue ("FrequenciesForRegionModes", QVariant::fromValue (frequencies_.frequency_list ())); settings_->setValue ("stations", QVariant::fromValue (stations_.station_list ())); + settings_->setValue ("FrequenciesForRegionModes_v2", QVariant::fromValue (frequencies_.frequency_list ())); settings_->setValue ("DecodeHighlighting", QVariant::fromValue (decode_highlighing_model_.items ())); settings_->setValue ("HighlightByMode", highlight_by_mode_); settings_->setValue ("OnlyFieldsSought", highlight_only_fields_); @@ -1989,7 +2079,6 @@ TransceiverFactory::ParameterPack Configuration::impl::gather_rig_data () void Configuration::impl::accept () { // Called when OK button is clicked. - if (!validate ()) { return; // not accepting @@ -2190,7 +2279,7 @@ void Configuration::impl::accept () if (frequencies_.frequency_list () != next_frequencies_.frequency_list ()) { frequencies_.frequency_list (next_frequencies_.frequency_list ()); - frequencies_.sort (FrequencyList_v2::frequency_column); + frequencies_.sort (FrequencyList_v2_101::frequency_column); } if (stations_.station_list () != next_stations_.station_list ()) @@ -2533,17 +2622,25 @@ void Configuration::impl::check_multicast (QHostAddress const& ha) udp_server_name_edited_ = false; } +void Configuration::impl::size_frequency_table_columns() +{ + ui_->frequencies_table_view->setVisible(false); + ui_->frequencies_table_view->resizeColumnsToContents(); + ui_->frequencies_table_view->setVisible(true); +} + void Configuration::impl::delete_frequencies () { auto selection_model = ui_->frequencies_table_view->selectionModel (); selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); next_frequencies_.removeDisjointRows (selection_model->selectedRows ()); - ui_->frequencies_table_view->resizeColumnToContents (FrequencyList_v2::mode_column); + ui_->frequencies_table_view->resizeColumnToContents (FrequencyList_v2_101::mode_column); + size_frequency_table_columns (); } void Configuration::impl::load_frequencies () { - auto file_name = QFileDialog::getOpenFileName (this, tr ("Load Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg);;All files (*.*)")); + auto file_name = QFileDialog::getOpenFileName (this, tr ("Load Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg *.qrg.json);;All files (*.*)")); if (!file_name.isNull ()) { auto const list = read_frequencies_file (file_name); @@ -2557,24 +2654,42 @@ void Configuration::impl::load_frequencies () { next_frequencies_.frequency_list (list); // update the model } + size_frequency_table_columns(); } } void Configuration::impl::merge_frequencies () { - auto file_name = QFileDialog::getOpenFileName (this, tr ("Merge Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg);;All files (*.*)")); + auto file_name = QFileDialog::getOpenFileName (this, tr ("Merge Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg *.qrg.json);;All files (*.*)")); if (!file_name.isNull ()) { next_frequencies_.frequency_list_merge (read_frequencies_file (file_name)); // update the model + size_frequency_table_columns(); } } -FrequencyList_v2::FrequencyItems Configuration::impl::read_frequencies_file (QString const& file_name) +FrequencyList_v2_101::FrequencyItems Configuration::impl::read_frequencies_file (QString const& file_name) { QFile frequencies_file {file_name}; frequencies_file.open (QFile::ReadOnly); QDataStream ids {&frequencies_file}; - FrequencyList_v2::FrequencyItems list; + FrequencyList_v2_101::FrequencyItems list; + FrequencyList_v2::FrequencyItems list_v100; + + // read file as json if ends with qrg.json. + if (file_name.endsWith(".qrg.json", Qt::CaseInsensitive)) + { + try + { + list = FrequencyList_v2_101::from_json_file(&frequencies_file); + } + catch (ReadFileException const &e) + { + MessageBox::critical_message(this, tr("Error reading frequency file"), e.message_); + } + return list; + } + quint32 magic; ids >> magic; if (qrg_magic != magic) @@ -2593,8 +2708,20 @@ FrequencyList_v2::FrequencyItems Configuration::impl::read_frequencies_file (QSt } // de-serialize the data using version if necessary to - // handle old schemata - ids >> list; + // handle old schema + if (version == qrg_version_100) + { + ids >> list_v100; + Q_FOREACH (auto const& item, list_v100) + { + list << FrequencyList_v2_101::Item{item.frequency_, item.mode_, item.region_, QString(), QString(), QDateTime(), + QDateTime(), false}; + } + } + else + { + ids >> list; + } if (ids.status () != QDataStream::Ok || !ids.atEnd ()) { @@ -2602,18 +2729,21 @@ FrequencyList_v2::FrequencyItems Configuration::impl::read_frequencies_file (QSt list.clear (); return list; } - return list; } void Configuration::impl::save_frequencies () { - auto file_name = QFileDialog::getSaveFileName (this, tr ("Save Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg);;All files (*.*)")); + auto file_name = QFileDialog::getSaveFileName (this, tr ("Save Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg *.qrg.json);;All files (*.*)")); if (!file_name.isNull ()) { + bool b_write_json = file_name.endsWith(".qrg.json", Qt::CaseInsensitive); + QFile frequencies_file {file_name}; frequencies_file.open (QFile::WriteOnly); + QDataStream ods {&frequencies_file}; + auto selection_model = ui_->frequencies_table_view->selectionModel (); if (selection_model->hasSelection () && MessageBox::Yes == MessageBox::query_message (this @@ -2623,11 +2753,28 @@ void Configuration::impl::save_frequencies () "Click No to save all."))) { selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - ods << qrg_magic << qrg_version << next_frequencies_.frequency_list (selection_model->selectedRows ()); + if (b_write_json) + { + next_frequencies_.to_json_file(&frequencies_file, "0x" + QString::number(qrg_magic, 16).toUpper(), + "0x" + QString::number(qrg_version, 16).toUpper(), + next_frequencies_.frequency_list(selection_model->selectedRows())); + } else + { + ods << qrg_magic << qrg_version << next_frequencies_.frequency_list(selection_model->selectedRows()); + } } else { - ods << qrg_magic << qrg_version << next_frequencies_.frequency_list (); + if (b_write_json) + { + next_frequencies_.to_json_file(&frequencies_file, + "0x" + QString::number(qrg_magic, 16).toUpper(), + "0x" + QString::number(qrg_version, 16).toUpper(), + next_frequencies_.frequency_list()); + } else + { + ods << qrg_magic << qrg_version << next_frequencies_.frequency_list(); + } } } } @@ -2641,6 +2788,7 @@ void Configuration::impl::reset_frequencies () { next_frequencies_.reset_to_defaults (); } + size_frequency_table_columns (); } void Configuration::impl::insert_frequency () @@ -2648,7 +2796,8 @@ void Configuration::impl::insert_frequency () if (QDialog::Accepted == frequency_dialog_->exec ()) { ui_->frequencies_table_view->setCurrentIndex (next_frequencies_.add (frequency_dialog_->item ())); - ui_->frequencies_table_view->resizeColumnToContents (FrequencyList_v2::mode_column); + ui_->frequencies_table_view->resizeColumnToContents (FrequencyList_v2_101::mode_column); + size_frequency_table_columns(); } } diff --git a/Configuration.hpp b/Configuration.hpp index cb32f0365..85b887ae7 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -18,7 +18,7 @@ class QAudioDeviceInfo; class QDir; class QNetworkAccessManager; class Bands; -class FrequencyList_v2; +class FrequencyList_v2_101; class StationList; class QStringListModel; class LotWUsers; @@ -164,8 +164,8 @@ public: Bands * bands (); Bands const * bands () const; IARURegions::Region region () const; - FrequencyList_v2 * frequencies (); - FrequencyList_v2 const * frequencies () const; + FrequencyList_v2_101 * frequencies (); + FrequencyList_v2_101 const * frequencies () const; StationList * stations (); StationList const * stations () const; QStringListModel * macros (); diff --git a/MetaDataRegistry.cpp b/MetaDataRegistry.cpp index c39551fa1..f6c50c1f1 100644 --- a/MetaDataRegistry.cpp +++ b/MetaDataRegistry.cpp @@ -52,9 +52,13 @@ void register_types () item_editor_factory->registerEditor (qMetaTypeId (), new QStandardItemEditorCreator ()); - // Frequency list model + // V101 Frequency list model + qRegisterMetaTypeStreamOperators ("Item_v2_101"); + QMetaType::registerConverter (&FrequencyList_v2_101::Item::toString); + qRegisterMetaTypeStreamOperators ("FrequencyItems_v2_101"); + + // V100 Frequency list model qRegisterMetaTypeStreamOperators ("Item_v2"); - QMetaType::registerConverter (&FrequencyList_v2::Item::toString); qRegisterMetaTypeStreamOperators ("FrequencyItems_v2"); // defunct old versions diff --git a/NEWS b/NEWS index 9381bd0b2..9d428ff57 100644 --- a/NEWS +++ b/NEWS @@ -11,11 +11,103 @@ Copyright 2001 - 2022 by Joe Taylor, K1JT, and the WSJT Development Team + Release: WSJT-X 2.6.0-rc5 + November 29, 2022 + ------------------------- + +WSJT-X 2.6.0 Release Candidate 5 brings a number of improvements as +well as some bug fixes. + +In program WSJT-X: + + - Better calibration for FST4/W SNR estimates. + + - Improved FT8 decoding on crowded bands. + + - The Working frequency table now offers the following options: + - Better handling of more than one frequency per band. + - Set your preferred frequencies, WSJT-X always comes back to these + QRGs when changing band or mode. + - You can label your frequencies with discriptions (e.g. DXp AB0YXZ). + - Option to set Start and End Date/Time, so that the frequencies + automatically appear and disappear. Useful for contest or + DXpedition QRGs. + - Load a frequency table from a file to easily get all such data + implemented. + + - In Fox mode, there are now a few additional functions that allow + operators to react even better to different QSO situations: + - A new two-column table in Tab 2 provides a better overview of + the queue and of the callsigns with QSOs in progress. + - Fox operator can now change the ordering of callsigns in the + queue so that he can react if there is only a narrow time slot + for a particular QSO due to propagation. + - Fox now responds for another two cycles to stations whose + report was not received, increasing the success rate for a + difficult QSO. + + - Correct a flaw in Hound mode that sometimes prevented completion of + QSOs with non-standard calls. + + - Improvements to EME Echo mode: + - New control "Avg" sets the number of Echo cycles to be averaged. + - New capability to display measurements of wideband noise power as + a function of time. This can be useful for measuring Sun noise, + antenna tracking errors, and the like. + - Several minor bug fixes + + - Correct a flaw that prevented WSJT-X from always initializing + correctly when special operating activities were enabled. + + - Correct a flaw that caused wrong Tx frequencies to be written to + ALL.TXT. + + - The GUI has been improved regarding some details. The controls now + scale better at different font sizes and on different operating + systems. + + - When in Hound mode and click the "H" button again, the frequency + is now kept. This gives the user the following two options to return + to normal FT8 mode: + - Click the "H" button again. Then you will stay on the QRG. + - Click the "FT8" button (or use the Settings menu). It brings + you back to the default FT8 QRG. + + - Decodes flagged as 'a7' are now disabled when "Enable AP" is not + checked, and during the first period after a band change. + + - The network address of the UDP server is now checked for errors + before being accepted. + + - Some improvements to the DX Lab Suite Commander interface. + + - Correct some possible flaws that could cause segfault errors in + certain unusual situations. + + - Warnings on dropped audio have been disabled for now, as detection + has turned out to be not reliable enough. + + - Correct a long-standing flaw which caused the "Transmit digital gain" + overlay toappear somewhere on the screen. + + - "Highlight DX Call" now also works when the other station is <...>. + + - CQ messages without a grid are now sent to PSK reporter. + + - Several other minor corrections (tool tips, etc.). + +Program MAP65 (Windows only) includes several minor bug fixes and two +tentative new features: + - an aid for measuring antenna pointing errors + - an ability to read the file wsjtx.log (kept by WSJT-X) to recognize + EME contest dupes. + Release: WSJT-X 2.6.0-rc4 September 8, 2022 ------------------------- -WSJT-X 2.6.0 Release Candidate 4 provides further improvements to Echo mode, a new File menu item, and several bug fixes. +WSJT-X 2.6.0 Release Candidate 4 provides further improvements to Echo +mode, a new File menu item, and several bug fixes. - New features and fixes for Echo mode - Created a new simulator echosim[.exe] diff --git a/Release_Notes.txt b/Release_Notes.txt index f18beaccd..bfcbf0d76 100644 --- a/Release_Notes.txt +++ b/Release_Notes.txt @@ -11,11 +11,103 @@ Copyright 2001 - 2022 by Joe Taylor, K1JT, and the WSJT Development Team + Release: WSJT-X 2.6.0-rc5 + November 29, 2022 + ------------------------- + +WSJT-X 2.6.0 Release Candidate 5 brings a number of improvements as +well as some bug fixes. + +In program WSJT-X: + + - Better calibration for FST4/W SNR estimates. + + - Improved FT8 decoding on crowded bands. + + - The Working frequency table now offers the following options: + - Better handling of more than one frequency per band. + - Set your preferred frequencies, WSJT-X always comes back to these + QRGs when changing band or mode. + - You can label your frequencies with discriptions (e.g. DXp AB0YXZ). + - Option to set Start and End Date/Time, so that the frequencies + automatically appear and disappear. Useful for contest or + DXpedition QRGs. + - Load a frequency table from a file to easily get all such data + implemented. + + - In Fox mode, there are now a few additional functions that allow + operators to react even better to different QSO situations: + - A new two-column table in Tab 2 provides a better overview of + the queue and of the callsigns with QSOs in progress. + - Fox operator can now change the ordering of callsigns in the + queue so that he can react if there is only a narrow time slot + for a particular QSO due to propagation. + - Fox now responds for another two cycles to stations whose + report was not received, increasing the success rate for a + difficult QSO. + + - Correct a flaw in Hound mode that sometimes prevented completion of + QSOs with non-standard calls. + + - Improvements to EME Echo mode: + - New control "Avg" sets the number of Echo cycles to be averaged. + - New capability to display measurements of wideband noise power as + a function of time. This can be useful for measuring Sun noise, + antenna tracking errors, and the like. + - Several minor bug fixes + + - Correct a flaw that prevented WSJT-X from always initializing + correctly when special operating activities were enabled. + + - Correct a flaw that caused wrong Tx frequencies to be written to + ALL.TXT. + + - The GUI has been improved regarding some details. The controls now + scale better at different font sizes and on different operating + systems. + + - When in Hound mode and click the "H" button again, the frequency + is now kept. This gives the user the following two options to return + to normal FT8 mode: + - Click the "H" button again. Then you will stay on the QRG. + - Click the "FT8" button (or use the Settings menu). It brings + you back to the default FT8 QRG. + + - Decodes flagged as 'a7' are now disabled when "Enable AP" is not + checked, and during the first period after a band change. + + - The network address of the UDP server is now checked for errors + before being accepted. + + - Some improvements to the DX Lab Suite Commander interface. + + - Correct some possible flaws that could cause segfault errors in + certain unusual situations. + + - Warnings on dropped audio have been disabled for now, as detection + has turned out to be not reliable enough. + + - Correct a long-standing flaw which caused the "Transmit digital gain" + overlay toappear somewhere on the screen. + + - "Highlight DX Call" now also works when the other station is <...>. + + - CQ messages without a grid are now sent to PSK reporter. + + - Several other minor corrections (tool tips, etc.). + +Program MAP65 (Windows only) includes several minor bug fixes and two +tentative new features: + - an aid for measuring antenna pointing errors + - an ability to read the file wsjtx.log (kept by WSJT-X) to recognize + EME contest dupes. + Release: WSJT-X 2.6.0-rc4 September 8, 2022 ------------------------- -WSJT-X 2.6.0 Release Candidate 4 provides further improvements to Echo mode, a new File menu item, and several bug fixes. +WSJT-X 2.6.0 Release Candidate 4 provides further improvements to Echo +mode, a new File menu item, and several bug fixes. - New features and fixes for Echo mode - Created a new simulator echosim[.exe] diff --git a/WSPR/WSPRBandHopping.hpp b/WSPR/WSPRBandHopping.hpp index 9af27b5f3..9f65de177 100644 --- a/WSPR/WSPRBandHopping.hpp +++ b/WSPR/WSPRBandHopping.hpp @@ -41,7 +41,7 @@ class QWidget; // storage using the provided QSettings object instance. // // A passed in Configuration object instance is used to query the -// FrequencyList_v2 model to determine working frequencies for each +// FrequencyList_v2_101 model to determine working frequencies for each // band. The row index of this model is returned by this classes // hopping scheduling method so it may be conveniently used to select // a new working frequency by a client. diff --git a/item_delegates/MessageItemDelegate.cpp b/item_delegates/MessageItemDelegate.cpp new file mode 100644 index 000000000..df9953e45 --- /dev/null +++ b/item_delegates/MessageItemDelegate.cpp @@ -0,0 +1,26 @@ +// +// Moved from Configuration.cpp +// + +#include "MessageItemDelegate.hpp" + +#include +#include + +// +// Class MessageItemDelegate +// +// Item delegate for message entry such as free text message macros. +// +MessageItemDelegate::MessageItemDelegate(QObject *parent): QStyledItemDelegate{parent} +{ +} + +QWidget *MessageItemDelegate::createEditor(QWidget *parent, QStyleOptionViewItem const &, QModelIndex const &) const +{ + QRegularExpression message_alphabet{"[- @A-Za-z0-9+./?#<>;$]*"}; + auto editor = new QLineEdit{parent}; + editor->setFrame(false); + editor->setValidator(new QRegularExpressionValidator{message_alphabet, editor}); + return editor; +} diff --git a/item_delegates/MessageItemDelegate.hpp b/item_delegates/MessageItemDelegate.hpp new file mode 100644 index 000000000..eede221c1 --- /dev/null +++ b/item_delegates/MessageItemDelegate.hpp @@ -0,0 +1,20 @@ +// +// +// + +#ifndef WSJTX_MESSAGEITEMDELEGATE_H +#define WSJTX_MESSAGEITEMDELEGATE_H + +#include + +class MessageItemDelegate: public QStyledItemDelegate + { + Q_OBJECT + +public: + explicit MessageItemDelegate(QObject *parent = nullptr); + QWidget *createEditor(QWidget *parent, QStyleOptionViewItem const & /* option*/ + , QModelIndex const & /* index */ + ) const override; +}; +#endif //WSJTX_MESSAGEITEMDELEGATE_H diff --git a/models/Bands.hpp b/models/Bands.hpp index 170d4c756..77ae5766b 100644 --- a/models/Bands.hpp +++ b/models/Bands.hpp @@ -24,7 +24,7 @@ // // Implements the QAbstractTableModel interface as an immutable table // where rows are bands and columns are band name, lower frequency -// limit and, upper ferquency limit respectively. +// limit and, upper frequency limit respectively. // class Bands final : public QAbstractTableModel diff --git a/models/FrequencyList.cpp b/models/FrequencyList.cpp index cac85ea5a..323dda126 100644 --- a/models/FrequencyList.cpp +++ b/models/FrequencyList.cpp @@ -5,6 +5,7 @@ #include #include + #include #include #include @@ -17,51 +18,59 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include "Radio.hpp" #include "Bands.hpp" #include "pimpl_impl.hpp" +#include "revision_utils.hpp" +#include "Logger.hpp" #include "moc_FrequencyList.cpp" namespace { - FrequencyList_v2::FrequencyItems const default_frequency_list = + FrequencyList_v2_101::FrequencyItems const default_frequency_list = { - {198000, Modes::FreqCal, IARURegions::R1}, // BBC Radio 4 Droitwich - {4996000, Modes::FreqCal, IARURegions::R1}, // RWM time signal - {9996000, Modes::FreqCal, IARURegions::R1}, // RWM time signal - {14996000, Modes::FreqCal, IARURegions::R1}, // RWM time signal + {198000, Modes::FreqCal, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, // BBC Radio 4 Droitwich + {4996000, Modes::FreqCal, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, // RWM time signal + {9996000, Modes::FreqCal, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, // RWM time signal + {14996000, Modes::FreqCal, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, // RWM time signal - {660000, Modes::FreqCal, IARURegions::R2}, - {880000, Modes::FreqCal, IARURegions::R2}, - {1210000, Modes::FreqCal, IARURegions::R2}, + {660000, Modes::FreqCal, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {880000, Modes::FreqCal, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {1210000, Modes::FreqCal, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, - {2500000, Modes::FreqCal, IARURegions::ALL}, - {3330000, Modes::FreqCal, IARURegions::ALL}, - {5000000, Modes::FreqCal, IARURegions::ALL}, - {7850000, Modes::FreqCal, IARURegions::ALL}, - {10000000, Modes::FreqCal, IARURegions::ALL}, - {14670000, Modes::FreqCal, IARURegions::ALL}, - {15000000, Modes::FreqCal, IARURegions::ALL}, - {20000000, Modes::FreqCal, IARURegions::ALL}, + {2500000, Modes::FreqCal, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {3330000, Modes::FreqCal, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {5000000, Modes::FreqCal, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {7850000, Modes::FreqCal, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {10000000, Modes::FreqCal, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {14670000, Modes::FreqCal, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {15000000, Modes::FreqCal, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {20000000, Modes::FreqCal, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, - {136000, Modes::WSPR, IARURegions::ALL}, - {136000, Modes::FST4, IARURegions::ALL}, - {136000, Modes::FST4W, IARURegions::ALL}, - {136000, Modes::JT9, IARURegions::ALL}, + {136000, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {136000, Modes::FST4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {136000, Modes::FST4W, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {136000, Modes::JT9, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {474200, Modes::JT9, IARURegions::ALL}, - {474200, Modes::FST4, IARURegions::ALL}, - {474200, Modes::WSPR, IARURegions::ALL}, - {474200, Modes::FST4W, IARURegions::ALL}, + {474200, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {474200, Modes::FST4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {474200, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {474200, Modes::FST4W, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, - {1836600, Modes::WSPR, IARURegions::ALL}, - {1836800, Modes::FST4W, IARURegions::ALL}, - {1838000, Modes::JT65, IARURegions::ALL}, // squeezed allocations - {1839000, Modes::JT9, IARURegions::ALL}, - {1839000, Modes::FST4, IARURegions::ALL}, - {1840000, Modes::FT8, IARURegions::ALL}, + {1836600, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {1836800, Modes::FST4W, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {1838000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // squeezed allocations + {1839000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {1839000, Modes::FST4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {1840000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // Band plans (all USB dial unless stated otherwise) // @@ -90,12 +99,12 @@ namespace // 3580 PSK31 // 3600 LSB EMCOMM // - {3570000, Modes::JT65, IARURegions::ALL}, // JA compatible - {3572000, Modes::JT9, IARURegions::ALL}, - {3573000, Modes::FT8, IARURegions::ALL}, // above as below JT65 is out of DM allocation - {3568600, Modes::WSPR, IARURegions::ALL}, // needs guard marker and lock out - {3575000, Modes::FT4, IARURegions::ALL}, // provisional - {3568000, Modes::FT4, IARURegions::R3}, // provisional + {3570000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // JA compatible + {3572000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {3573000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // above as below JT65 is out of DM allocation + {3568600, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // needs guard marker and lock out + {3575000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // provisional + {3568000, Modes::FT4, IARURegions::R3, "","", QDateTime(), QDateTime(), false}, // provisional // Band plans (all USB dial unless stated otherwise) // @@ -128,11 +137,11 @@ namespace // 7090 LSB QRP CoA // 7110 LSB EMCOMM // - {7038600, Modes::WSPR, IARURegions::ALL}, - {7074000, Modes::FT8, IARURegions::ALL}, - {7076000, Modes::JT65, IARURegions::ALL}, - {7078000, Modes::JT9, IARURegions::ALL}, - {7047500, Modes::FT4, IARURegions::ALL}, // provisional - moved + {7038600, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {7074000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {7076000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {7078000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {7047500, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // provisional - moved // up 500Hz to clear // W1AW code practice QRG @@ -162,11 +171,11 @@ namespace // 10142.25 OLIVIA, Contestia, etc. // 10143.25 OLIVIA, Contestia, etc. (main QRG) // - {10136000, Modes::FT8, IARURegions::ALL}, - {10138000, Modes::JT65, IARURegions::ALL}, - {10138700, Modes::WSPR, IARURegions::ALL}, - {10140000, Modes::JT9, IARURegions::ALL}, - {10140000, Modes::FT4, IARURegions::ALL}, // provisional + {10136000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {10138000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {10138700, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {10140000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {10140000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // provisional // Band plans (all USB dial unless stated otherwise) // @@ -205,11 +214,11 @@ namespace // 14105.5 OLIVIA 1000 // 14106.5 OLIVIA 1000 (main QRG) // - {14095600, Modes::WSPR, IARURegions::ALL}, - {14074000, Modes::FT8, IARURegions::ALL}, - {14076000, Modes::JT65, IARURegions::ALL}, - {14078000, Modes::JT9, IARURegions::ALL}, - {14080000, Modes::FT4, IARURegions::ALL}, // provisional + {14095600, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {14074000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {14076000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {14078000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {14080000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // provisional // Band plans (all USB dial unless stated otherwise) // @@ -238,148 +247,179 @@ namespace // 18104.4 OLIVIA, Contestia, etc. // 18110 NCDXF beacons // - {18100000, Modes::FT8, IARURegions::ALL}, - {18102000, Modes::JT65, IARURegions::ALL}, - {18104000, Modes::JT9, IARURegions::ALL}, - {18104000, Modes::FT4, IARURegions::ALL}, // provisional - {18104600, Modes::WSPR, IARURegions::ALL}, + {18100000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {18102000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {18104000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {18104000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // provisional + {18104600, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, - {21074000, Modes::FT8, IARURegions::ALL}, - {21076000, Modes::JT65, IARURegions::ALL}, - {21078000, Modes::JT9, IARURegions::ALL}, - {21094600, Modes::WSPR, IARURegions::ALL}, - {21140000, Modes::FT4, IARURegions::ALL}, + {21074000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {21076000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {21078000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {21094600, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {21140000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, - {24915000, Modes::FT8, IARURegions::ALL}, - {24917000, Modes::JT65, IARURegions::ALL}, - {24919000, Modes::JT9, IARURegions::ALL}, - {24919000, Modes::FT4, IARURegions::ALL}, // provisional - {24924600, Modes::WSPR, IARURegions::ALL}, + {24915000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {24917000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {24919000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {24919000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // provisional + {24924600, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, - {28074000, Modes::FT8, IARURegions::ALL}, - {28076000, Modes::JT65, IARURegions::ALL}, - {28078000, Modes::JT9, IARURegions::ALL}, - {28124600, Modes::WSPR, IARURegions::ALL}, - {28180000, Modes::FT4, IARURegions::ALL}, + {28074000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {28076000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {28078000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {28124600, Modes::WSPR, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {28180000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, - {50200000, Modes::Echo, IARURegions::ALL}, - {50211000, Modes::Q65, IARURegions::ALL}, - {50275000, Modes::Q65, IARURegions::ALL}, - {50276000, Modes::JT65, IARURegions::R2}, - {50276000, Modes::JT65, IARURegions::R3}, - {50380000, Modes::MSK144, IARURegions::R1}, - {50260000, Modes::MSK144, IARURegions::R2}, - {50260000, Modes::MSK144, IARURegions::R3}, - {50293000, Modes::WSPR, IARURegions::R2}, - {50293000, Modes::WSPR, IARURegions::R3}, - {50310000, Modes::JT65, IARURegions::ALL}, - {50312000, Modes::JT9, IARURegions::ALL}, - {50313000, Modes::FT8, IARURegions::ALL}, - {50318000, Modes::FT4, IARURegions::ALL}, // provisional - {50323000, Modes::FT8, IARURegions::ALL}, + {50200000, Modes::Echo, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {50211000, Modes::Q65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {50275000, Modes::Q65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {50276000, Modes::JT65, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {50276000, Modes::JT65, IARURegions::R3, "","", QDateTime(), QDateTime(), false}, + {50380000, Modes::MSK144, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, + {50260000, Modes::MSK144, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {50260000, Modes::MSK144, IARURegions::R3, "","", QDateTime(), QDateTime(), false}, + {50293000, Modes::WSPR, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {50293000, Modes::WSPR, IARURegions::R3, "","", QDateTime(), QDateTime(), false}, + {50310000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {50312000, Modes::JT9, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {50313000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {50318000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, // provisional + {50323000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, - {70102000, Modes::JT65, IARURegions::R1}, - {70104000, Modes::JT9, IARURegions::R1}, - {70091000, Modes::WSPR, IARURegions::R1}, - {70154000, Modes::FT8, IARURegions::R1}, - {70230000, Modes::MSK144, IARURegions::R1}, + {70102000, Modes::JT65, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, + {70104000, Modes::JT9, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, + {70091000, Modes::WSPR, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, + {70154000, Modes::FT8, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, + {70230000, Modes::MSK144, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, - {144116000, Modes::Q65, IARURegions::ALL}, - {144120000, Modes::JT65, IARURegions::ALL}, - {144120000, Modes::Echo, IARURegions::ALL}, - {144170000, Modes::FT4, IARURegions::ALL}, - {144174000, Modes::FT8, IARURegions::ALL}, - {144360000, Modes::MSK144, IARURegions::R1}, - {144150000, Modes::MSK144, IARURegions::R2}, - {144489000, Modes::WSPR, IARURegions::ALL}, + {144116000, Modes::Q65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {144120000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {144120000, Modes::Echo, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {144170000, Modes::FT4, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {144174000, Modes::FT8, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {144360000, Modes::MSK144, IARURegions::R1, "","", QDateTime(), QDateTime(), false}, + {144150000, Modes::MSK144, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {144489000, Modes::WSPR, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {222065000, Modes::Echo, IARURegions::R2}, - {222065000, Modes::JT65, IARURegions::R2}, - {222065000, Modes::Q65, IARURegions::R2}, + {222065000, Modes::Echo, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {222065000, Modes::JT65, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {222065000, Modes::Q65, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, - {432065000, Modes::Echo, IARURegions::ALL}, - {432065000, Modes::JT65, IARURegions::ALL}, - {432300000, Modes::WSPR, IARURegions::ALL}, - {432360000, Modes::MSK144, IARURegions::ALL}, - {432065000, Modes::Q65, IARURegions::ALL}, + {432065000, Modes::Echo, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {432065000, Modes::JT65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {432300000, Modes::WSPR, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {432360000, Modes::MSK144, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {432065000, Modes::Q65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {902065000, Modes::JT65, IARURegions::R2}, - {902065000, Modes::Q65, IARURegions::R2}, + {902065000, Modes::JT65, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, + {902065000, Modes::Q65, IARURegions::R2, "","", QDateTime(), QDateTime(), false}, - {1296065000, Modes::Echo, IARURegions::ALL}, - {1296065000, Modes::JT65, IARURegions::ALL}, - {1296500000, Modes::WSPR, IARURegions::ALL}, - {1296065000, Modes::Q65, IARURegions::ALL}, + {1296065000, Modes::Echo, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {1296065000, Modes::JT65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {1296500000, Modes::WSPR, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {1296065000, Modes::Q65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {2301000000, Modes::Echo, IARURegions::ALL}, - {2301065000, Modes::JT4, IARURegions::ALL}, - {2301065000, Modes::JT65, IARURegions::ALL}, - {2301065000, Modes::Q65, IARURegions::ALL}, + {2301000000, Modes::Echo, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {2301065000, Modes::JT4, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {2301065000, Modes::JT65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {2301065000, Modes::Q65, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, - {2304065000, Modes::Echo, IARURegions::ALL}, - {2304065000, Modes::JT4, IARURegions::ALL}, - {2304065000, Modes::JT65, IARURegions::ALL}, - {2304065000, Modes::Q65, IARURegions::ALL}, + {2304065000, Modes::Echo, IARURegions::ALL, "","", QDateTime(), QDateTime(), false}, + {2304065000, Modes::JT4, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {2304065000, Modes::JT65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {2304065000, Modes::Q65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {2320065000, Modes::Echo, IARURegions::ALL}, - {2320065000, Modes::JT4, IARURegions::ALL}, - {2320065000, Modes::JT65, IARURegions::ALL}, - {2320065000, Modes::Q65, IARURegions::ALL}, + {2320065000, Modes::Echo, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {2320065000, Modes::JT4, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {2320065000, Modes::JT65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {2320065000, Modes::Q65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {3400065000, Modes::Echo, IARURegions::ALL}, - {3400065000, Modes::JT4, IARURegions::ALL}, - {3400065000, Modes::JT65, IARURegions::ALL}, - {3400065000, Modes::Q65, IARURegions::ALL}, + {3400065000, Modes::Echo, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {3400065000, Modes::JT4, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {3400065000, Modes::JT65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {3400065000, Modes::Q65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {5760065000, Modes::Echo, IARURegions::ALL}, - {5760065000, Modes::JT4, IARURegions::ALL}, - {5760065000, Modes::JT65, IARURegions::ALL}, - {5760200000, Modes::Q65, IARURegions::ALL}, + {5760065000, Modes::Echo, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {5760065000, Modes::JT4, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {5760065000, Modes::JT65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {5760200000, Modes::Q65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {10368100000, Modes::Echo, IARURegions::ALL}, - {10368200000, Modes::JT4, IARURegions::ALL}, - {10368200000, Modes::Q65, IARURegions::ALL}, + {10368100000, Modes::Echo, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {10368200000, Modes::JT4, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {10368200000, Modes::Q65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, - {24048100000, Modes::Echo, IARURegions::ALL}, - {24048200000, Modes::JT4, IARURegions::ALL}, - {24048200000, Modes::Q65, IARURegions::ALL}, + {24048100000, Modes::Echo, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {24048200000, Modes::JT4, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, + {24048200000, Modes::Q65, IARURegions::ALL,"","", QDateTime(), QDateTime(), false}, }; } #if !defined (QT_NO_DEBUG_STREAM) -QDebug operator << (QDebug debug, FrequencyList_v2::Item const& item) +QDebug operator << (QDebug debug, FrequencyList_v2_101::Item const& item) { QDebugStateSaver saver {debug}; return debug.nospace () << item.toString (); } #endif +bool FrequencyList_v2_101::Item::isSane() const +{ + return frequency_ > 0.0 && (!start_time_.isValid() || !end_time_.isValid() || start_time_ < end_time_) + && (region_ == IARURegions::ALL || region_ == IARURegions::R1 || region_ == IARURegions::R2 || region_ == IARURegions::R3); +} -QString FrequencyList_v2::Item::toString () const +QString FrequencyList_v2_101::Item::toString () const { QString string; QTextStream qts {&string}; qts << "FrequencyItem(" << Radio::frequency_MHz_string (frequency_) << ", " << IARURegions::name (region_) << ", " - << Modes::name (mode_) << ')'; + << Modes::name (mode_) << ", " + << start_time_.toString(Qt::ISODate) << ", " + << end_time_.toString(Qt::ISODate) << ", " + << description_ << ", " + << source_ << "," + << preferred_ << ')'; return string; } -QDataStream& operator << (QDataStream& os, FrequencyList_v2::Item const& item) +QJsonObject FrequencyList_v2_101::Item::toJson() const { + return {{"frequency", Radio::frequency_MHz_string (frequency_) }, + {"mode", Modes::name (mode_) }, + {"region", IARURegions::name (region_)}, + {"description", description_}, + {"source", source_}, + {"start_time", start_time_.toString(Qt::ISODate) }, + {"end_time", end_time_.toString(Qt::ISODate) }, + {"preferred", preferred_}}; +} + +QDataStream& operator << (QDataStream& os, FrequencyList_v2_101::Item const& item) { return os << item.frequency_ << item.mode_ - << item.region_; + << item.region_ + << item.start_time_ + << item.end_time_ + << item.description_ + << item.source_ + << item.preferred_; } -QDataStream& operator >> (QDataStream& is, FrequencyList_v2::Item& item) +QDataStream& operator >> (QDataStream& is, FrequencyList_v2_101::Item& item) { return is >> item.frequency_ >> item.mode_ - >> item.region_; + >> item.region_ + >> item.start_time_ + >> item.end_time_ + >> item.description_ + >> item.source_ + >> item.preferred_; } -class FrequencyList_v2::impl final +class FrequencyList_v2_101::impl final : public QAbstractTableModel { public: @@ -388,6 +428,7 @@ public: , bands_ {bands} , region_filter_ {IARURegions::ALL} , mode_filter_ {Modes::ALL} + , filter_on_time_ {false} { } @@ -407,6 +448,8 @@ public: QStringList mimeTypes () const override; QMimeData * mimeData (QModelIndexList const&) const override; + void unprefer_all_but(Item & item, int const row, QVector ); + static int constexpr num_cols {SENTINAL}; static auto constexpr mime_type = "application/wsjt.Frequencies"; @@ -414,9 +457,11 @@ public: FrequencyItems frequency_list_; Region region_filter_; Mode mode_filter_; + bool filter_on_time_; + }; -FrequencyList_v2::FrequencyList_v2 (Bands const * bands, QObject * parent) +FrequencyList_v2_101::FrequencyList_v2_101 (Bands const * bands, QObject * parent) : QSortFilterProxyModel {parent} , m_ {bands, parent} { @@ -424,21 +469,21 @@ FrequencyList_v2::FrequencyList_v2 (Bands const * bands, QObject * parent) setSortRole (SortRole); } -FrequencyList_v2::~FrequencyList_v2 () +FrequencyList_v2_101::~FrequencyList_v2_101 () { } -auto FrequencyList_v2::frequency_list (FrequencyItems frequency_list) -> FrequencyItems +auto FrequencyList_v2_101::frequency_list (FrequencyItems frequency_list) -> FrequencyItems { return m_->frequency_list (frequency_list); } -auto FrequencyList_v2::frequency_list () const -> FrequencyItems const& +auto FrequencyList_v2_101::frequency_list () const -> FrequencyItems const& { return m_->frequency_list_; } -auto FrequencyList_v2::frequency_list (QModelIndexList const& model_index_list) const -> FrequencyItems +auto FrequencyList_v2_101::frequency_list (QModelIndexList const& model_index_list) const -> FrequencyItems { FrequencyItems list; Q_FOREACH (auto const& index, model_index_list) @@ -448,12 +493,13 @@ auto FrequencyList_v2::frequency_list (QModelIndexList const& model_index_list) return list; } -void FrequencyList_v2::frequency_list_merge (FrequencyItems const& items) +void FrequencyList_v2_101::frequency_list_merge (FrequencyItems const& items) { m_->add (items); } -int FrequencyList_v2::best_working_frequency (Frequency f) const + +int FrequencyList_v2_101::best_working_frequency (Frequency f) const { int result {-1}; auto const& target_band = m_->bands_->find (f); @@ -468,6 +514,11 @@ int FrequencyList_v2::best_working_frequency (Frequency f) const auto const& band = m_->bands_->find (candidate_frequency); if (band == target_band) { + // take the preferred one + if (m_->frequency_list_[source_row].preferred_) + { + return row; + } // take closest band match Radio::FrequencyDelta new_delta = f - candidate_frequency; if (std::abs (new_delta) < std::abs (delta)) @@ -481,7 +532,7 @@ int FrequencyList_v2::best_working_frequency (Frequency f) const return result; } -int FrequencyList_v2::best_working_frequency (QString const& target_band) const +int FrequencyList_v2_101::best_working_frequency (QString const& target_band) const { int result {-1}; if (!target_band.isEmpty ()) @@ -493,24 +544,26 @@ int FrequencyList_v2::best_working_frequency (QString const& target_band) const auto const& band = m_->bands_->find (m_->frequency_list_[source_row].frequency_); if (band == target_band) { - return row; + if (m_->frequency_list_[source_row].preferred_) + return row; // return the preferred one immediately + result = row; } } } return result; } -void FrequencyList_v2::reset_to_defaults () +void FrequencyList_v2_101::reset_to_defaults () { m_->frequency_list (default_frequency_list); } -QModelIndex FrequencyList_v2::add (Item f) +QModelIndex FrequencyList_v2_101::add (Item f) { return mapFromSource (m_->add (f)); } -bool FrequencyList_v2::remove (Item f) +bool FrequencyList_v2_101::remove (Item f) { auto row = m_->frequency_list_.indexOf (f); @@ -522,13 +575,13 @@ bool FrequencyList_v2::remove (Item f) return m_->removeRow (row); } -bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows) +bool FrequencyList_v2_101::removeDisjointRows (QModelIndexList rows) { bool result {true}; // 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. + // achieve that by processing them in descending row order. for (int r = 0; r < rows.size (); ++r) { rows[r] = mapToSource (rows[r]); @@ -549,14 +602,20 @@ bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows) return result; } -void FrequencyList_v2::filter (Region region, Mode mode) +void FrequencyList_v2_101::filter (Region region, Mode mode, bool filter_on_time) { m_->region_filter_ = region; m_->mode_filter_ = mode; + m_->filter_on_time_ = filter_on_time; invalidateFilter (); } -bool FrequencyList_v2::filterAcceptsRow (int source_row, QModelIndex const& /* parent */) const +void FrequencyList_v2_101::filter_refresh () +{ + invalidateFilter (); +} + +bool FrequencyList_v2_101::filterAcceptsRow (int source_row, QModelIndex const& /* parent */) const { bool result {true}; auto const& item = m_->frequency_list_[source_row]; @@ -570,11 +629,16 @@ bool FrequencyList_v2::filterAcceptsRow (int source_row, QModelIndex const& /* p result = (Modes::ALL == item.mode_ && m_->mode_filter_ != Modes::FreqCal) || m_->mode_filter_ == item.mode_; } + if (result && m_->filter_on_time_) + { + result = (!item.start_time_.isValid() || item.start_time_ <= QDateTime::currentDateTimeUtc ()) && + (!item.end_time_.isValid() || item.end_time_ >= QDateTime::currentDateTimeUtc ()); + } return result; } -auto FrequencyList_v2::impl::frequency_list (FrequencyItems frequency_list) -> FrequencyItems +auto FrequencyList_v2_101::impl::frequency_list (FrequencyItems frequency_list) -> FrequencyItems { beginResetModel (); std::swap (frequency_list_, frequency_list); @@ -583,7 +647,7 @@ auto FrequencyList_v2::impl::frequency_list (FrequencyItems frequency_list) -> F } // add a frequency returning the new model index -QModelIndex FrequencyList_v2::impl::add (Item f) +QModelIndex FrequencyList_v2_101::impl::add (Item f) { // Any Frequency that isn't in the list may be added if (!frequency_list_.contains (f)) @@ -594,12 +658,15 @@ QModelIndex FrequencyList_v2::impl::add (Item f) frequency_list_.append (f); endInsertRows (); + // if we added one that had a preferred frequency, unprefer everything else + unprefer_all_but(f, row, {Qt::DisplayRole, Qt::CheckStateRole}); + return index (row, 0); } return QModelIndex {}; } -void FrequencyList_v2::impl::add (FrequencyItems items) +void FrequencyList_v2_101::impl::add (FrequencyItems items) { // Any Frequency that isn't in the list may be added for (auto p = items.begin (); p != items.end ();) @@ -624,17 +691,17 @@ void FrequencyList_v2::impl::add (FrequencyItems items) } } -int FrequencyList_v2::impl::rowCount (QModelIndex const& parent) const +int FrequencyList_v2_101::impl::rowCount (QModelIndex const& parent) const { return parent.isValid () ? 0 : frequency_list_.size (); } -int FrequencyList_v2::impl::columnCount (QModelIndex const& parent) const +int FrequencyList_v2_101::impl::columnCount (QModelIndex const& parent) const { return parent.isValid () ? 0 : num_cols; } -Qt::ItemFlags FrequencyList_v2::impl::flags (QModelIndex const& index) const +Qt::ItemFlags FrequencyList_v2_101::impl::flags (QModelIndex const& index) const { auto result = QAbstractTableModel::flags (index) | Qt::ItemIsDropEnabled; auto row = index.row (); @@ -647,11 +714,16 @@ Qt::ItemFlags FrequencyList_v2::impl::flags (QModelIndex const& index) const { result |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled; } + + if (preferred_column == column) + { + result |= Qt::ItemIsUserCheckable; + } } return result; } -QVariant FrequencyList_v2::impl::data (QModelIndex const& index, int role) const +QVariant FrequencyList_v2_101::impl::data (QModelIndex const& index, int role) const { QVariant item; @@ -746,8 +818,14 @@ QVariant FrequencyList_v2::impl::data (QModelIndex const& index, int role) const case Qt::DisplayRole: { auto const& band = bands_->find (frequency_item.frequency_); - item = Radio::pretty_frequency_MHz_string (frequency_item.frequency_) - + " MHz (" + (band.isEmpty () ? "OOB" : band) + ')'; + QString desc_text; + desc_text = frequency_item.description_.isEmpty() ? "" : " \u2502 " + frequency_item.description_; + item = (frequency_item.preferred_ ? "\u2055 " : "") + + Radio::pretty_frequency_MHz_string(frequency_item.frequency_) + + " MHz (" + (band.isEmpty() ? "OOB" : band) + ")" + + (((frequency_item.start_time_.isValid() && !frequency_item.start_time_.isNull()) || + (frequency_item.end_time_.isValid() && !frequency_item.end_time_.isNull())) ? " \u2016 " : "") + + desc_text; } break; @@ -760,71 +838,328 @@ QVariant FrequencyList_v2::impl::data (QModelIndex const& index, int role) const item = Qt::AlignRight + Qt::AlignVCenter; break; } + break; + + case description_column: + switch (role) + { + case SortRole: + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::AccessibleTextRole: + item = frequency_item.description_; + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr("Description"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignLeft + Qt::AlignVCenter; + break; + } + break; + + case source_column: + switch (role) + { + case SortRole: + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::AccessibleTextRole: + item = frequency_item.source_; + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("Source"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignLeft + Qt::AlignVCenter; + break; + } + break; + + case start_time_column: + switch (role) + { + case SortRole: + item = frequency_item.start_time_; + break; + + case Qt::EditRole: + if (frequency_item.start_time_.isNull () || !frequency_item.start_time_.isValid ()) + { + item = QDateTime::currentDateTimeUtc ().toString (Qt::ISODate); + } + else + { + item = frequency_item.start_time_.toString(Qt::ISODate); + } + break; + + case Qt::DisplayRole: + case Qt::AccessibleTextRole: + item = frequency_item.start_time_.toString(Qt::ISODate); + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("Start Time"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignLeft + Qt::AlignVCenter; + break; + } + break; + + case end_time_column: + switch (role) + { + case SortRole: + item = frequency_item.end_time_; + break; + + case Qt::EditRole: + if (frequency_item.end_time_.isNull () || !frequency_item.end_time_.isValid ()) + { + item = QDateTime::currentDateTimeUtc ().toString (Qt::ISODate); + } + else + { + item = frequency_item.end_time_.toString(Qt::ISODate); + } + break; + + case Qt::DisplayRole: + case Qt::AccessibleTextRole: + item = frequency_item.end_time_.toString(Qt::ISODate); + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("End Time"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignLeft + Qt::AlignVCenter; + break; + } + break; + + case preferred_column: + switch (role) + { + case SortRole: + item = frequency_item.preferred_; + break; + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::AccessibleTextRole: + //item = frequency_item.preferred_ ? QString("True") : QString("False"); + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("Pref"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignHCenter + Qt::AlignVCenter; + break; + + case Qt::CheckStateRole: + item = frequency_item.preferred_ ? Qt::Checked : Qt::Unchecked; + break; + } break; } } return item; } -bool FrequencyList_v2::impl::setData (QModelIndex const& model_index, QVariant const& value, int role) +void FrequencyList_v2_101::impl::unprefer_all_but(Item &item, int const item_row, QVector roles) +{ + // un-prefer all of the other frequencies in this band + auto const band = bands_->find (item.frequency_); + if (band.isEmpty ()) return; // out of any band + + roles << Qt::CheckStateRole; + roles << Qt::DisplayRole; + + for (int row = 0; row < rowCount (); ++row) + { + if (row == item_row) continue; + + Item &i = frequency_list_[row]; + auto const &iter_band = bands_->find(i.frequency_); + + if (!iter_band.isEmpty() && band == iter_band && (i.region_ == item.region_) && (i.mode_ == item.mode_)) + { + i.preferred_ = false; + Q_EMIT dataChanged(index(row,preferred_column), index(row,preferred_column), roles); + } + } +} + +bool FrequencyList_v2_101::impl::setData (QModelIndex const& model_index, QVariant const& value, int role) { bool changed {false}; - auto const& row = model_index.row (); + auto& item = frequency_list_[row]; + + QVector roles; + roles << role; + + if (model_index.isValid () + && Qt::CheckStateRole == role + && row < frequency_list_.size () + && model_index.column () == preferred_column) + { + bool b_val = ((Qt::CheckState)value.toInt() == Qt::Checked); + if (b_val != item.preferred_) + { + item.preferred_ = b_val; + if (item.preferred_) + { + unprefer_all_but (item, row, roles); // un-prefer all of the other frequencies in this band + } + Q_EMIT dataChanged(index(row,description_column), index(row,preferred_column), roles); + changed = true; + } + } + if (model_index.isValid () && Qt::EditRole == role && row < frequency_list_.size ()) { - QVector roles; - roles << role; - - auto& item = frequency_list_[row]; - switch (model_index.column ()) + switch (model_index.column()) { - case region_column: - { - auto region = IARURegions::value (value.toString ()); - if (region != item.region_) - { - item.region_ = region; - Q_EMIT dataChanged (model_index, model_index, roles); - changed = true; - } - } - break; - - case mode_column: - { - auto mode = Modes::value (value.toString ()); - if (mode != item.mode_) - { - item.mode_ = mode; - Q_EMIT dataChanged (model_index, model_index, roles); - changed = true; - } - } - break; - - case frequency_column: - if (value.canConvert ()) + case region_column: { - Radio::Frequency frequency {qvariant_cast (value)}; - if (frequency != item.frequency_) + auto region = IARURegions::value(value.toString()); + if (region != item.region_) { - item.frequency_ = frequency; - // mark derived column (1) changed as well - Q_EMIT dataChanged (index (model_index.row (), 1), model_index, roles); + item.region_ = region; + Q_EMIT dataChanged(model_index, model_index, roles); changed = true; } } break; + + case mode_column: + { + auto mode = Modes::value(value.toString()); + if (mode != item.mode_) + { + item.mode_ = mode; + Q_EMIT dataChanged(model_index, model_index, roles); + changed = true; + } + } + break; + + case frequency_column: + { + if (value.canConvert()) + { + Radio::Frequency frequency{qvariant_cast(value)}; + if (frequency != item.frequency_) + { + item.frequency_ = frequency; + // mark derived column (1) changed as well + Q_EMIT dataChanged(index(model_index.row(), 1), model_index, roles); + changed = true; + } + } + } + break; + + case description_column: + { + if (value.toString() != item.description_) + { + item.description_ = value.toString(); + Q_EMIT dataChanged(model_index, model_index, roles); + changed = true; + } + } + break; + + case source_column: + { + if (value.toString() != item.source_) + { + item.source_ = value.toString(); + Q_EMIT dataChanged(model_index, model_index, roles); + changed = true; + } + } + break; + + case start_time_column: + { + QDateTime start_time = QDateTime::fromString(value.toString(), Qt::ISODate); + LOG_INFO(QString{"start_time = %1 - isEmpty %2"}.arg(value.toString()).arg(value.toString().isEmpty())); + if (value.toString().isEmpty()) + { // empty string is valid + start_time = QDateTime(); + } + if (start_time.isValid() || start_time.isNull()) + { + item.start_time_ = start_time; + if (item.end_time_.isValid() && !item.start_time_.isNull() && item.end_time_ < item.start_time_) + { + item.end_time_ = item.start_time_; + } + Q_EMIT dataChanged(model_index, index(model_index.row(), end_time_column), roles); + changed = true; + } + } + break; + + case end_time_column: + { + QDateTime end_time = QDateTime::fromString(value.toString(), Qt::ISODate); + if (value.toString().isEmpty()) + { // empty string is valid + end_time = QDateTime(); + } + if (end_time.isValid() || end_time.isNull()) + { + item.end_time_ = end_time; + if (item.start_time_.isValid() && !item.end_time_.isNull() && end_time <= item.start_time_) + { + item.start_time_ = end_time; + } + Q_EMIT dataChanged(index(model_index.row(), start_time_column), model_index, roles); + changed = true; + } + } + break; + + case preferred_column: + { + bool b_value = value.toBool(); + if (b_value != item.preferred_) + { + item.preferred_ = b_value; + Q_EMIT dataChanged(index(model_index.row(), start_time_column), model_index, roles); + changed = true; + } + } + break; + } } return changed; } -QVariant FrequencyList_v2::impl::headerData (int section, Qt::Orientation orientation, int role) const +QVariant FrequencyList_v2_101::impl::headerData (int section, Qt::Orientation orientation, int role) const { QVariant header; if (Qt::DisplayRole == role @@ -833,10 +1168,15 @@ QVariant FrequencyList_v2::impl::headerData (int section, Qt::Orientation orient { switch (section) { - case region_column: header = tr ("IARU Region"); break; - case mode_column: header = tr ("Mode"); break; - case frequency_column: header = tr ("Frequency"); break; - case frequency_mhz_column: header = tr ("Frequency (MHz)"); break; + case region_column: header = tr ("IARU Region"); break; + case mode_column: header = tr ("Mode"); break; + case frequency_column: header = tr ("Frequency"); break; + case frequency_mhz_column: header = tr ("Frequency (MHz)"); break; + case source_column: header = tr ("Source"); break; + case start_time_column: header = tr ("Start Date/Time"); break; + case end_time_column: header = tr ("End Date/Time"); break; + case preferred_column: header = tr ("Pref"); break; + case description_column: header = tr ("Description"); break; } } else @@ -846,7 +1186,7 @@ QVariant FrequencyList_v2::impl::headerData (int section, Qt::Orientation orient return header; } -bool FrequencyList_v2::impl::removeRows (int row, int count, QModelIndex const& parent) +bool FrequencyList_v2_101::impl::removeRows (int row, int count, QModelIndex const& parent) { if (0 < count && (row + count) <= rowCount (parent)) { @@ -861,14 +1201,14 @@ bool FrequencyList_v2::impl::removeRows (int row, int count, QModelIndex const& return false; } -bool FrequencyList_v2::impl::insertRows (int row, int count, QModelIndex const& parent) +bool FrequencyList_v2_101::impl::insertRows (int row, int count, QModelIndex const& parent) { if (0 < count) { beginInsertRows (parent, row, row + count - 1); for (auto r = 0; r < count; ++r) { - frequency_list_.insert (row, Item {0, Mode::ALL, IARURegions::ALL}); + frequency_list_.insert (row, Item {0, Mode::ALL, IARURegions::ALL, QString(), QString(), QDateTime(), QDateTime(), false}); } endInsertRows (); return true; @@ -876,14 +1216,14 @@ bool FrequencyList_v2::impl::insertRows (int row, int count, QModelIndex const& return false; } -QStringList FrequencyList_v2::impl::mimeTypes () const +QStringList FrequencyList_v2_101::impl::mimeTypes () const { QStringList types; types << mime_type; return types; } -QMimeData * FrequencyList_v2::impl::mimeData (QModelIndexList const& items) const +QMimeData * FrequencyList_v2_101::impl::mimeData (QModelIndexList const& items) const { QMimeData * mime_data = new QMimeData {}; QByteArray encoded_data; @@ -901,43 +1241,43 @@ QMimeData * FrequencyList_v2::impl::mimeData (QModelIndexList const& items) cons return mime_data; } -auto FrequencyList_v2::const_iterator::operator * () const -> Item const& +auto FrequencyList_v2_101::const_iterator::operator * () const -> Item const& { return parent_->frequency_list ().at(parent_->mapToSource (parent_->index (row_, 0)).row ()); } -auto FrequencyList_v2::const_iterator::operator -> () const -> Item const * +auto FrequencyList_v2_101::const_iterator::operator -> () const -> Item const * { return &parent_->frequency_list ().at(parent_->mapToSource (parent_->index (row_, 0)).row ()); } -bool FrequencyList_v2::const_iterator::operator != (const_iterator const& rhs) const +bool FrequencyList_v2_101::const_iterator::operator != (const_iterator const& rhs) const { return parent_ != rhs.parent_ || row_ != rhs.row_; } -bool FrequencyList_v2::const_iterator::operator == (const_iterator const& rhs) const +bool FrequencyList_v2_101::const_iterator::operator == (const_iterator const& rhs) const { return parent_ == rhs.parent_ && row_ == rhs.row_; } -auto FrequencyList_v2::const_iterator::operator ++ () -> const_iterator& +auto FrequencyList_v2_101::const_iterator::operator ++ () -> const_iterator& { ++row_; return *this; } -auto FrequencyList_v2::begin () const -> const_iterator +auto FrequencyList_v2_101::begin () const -> const_iterator { return const_iterator (this, 0); } -auto FrequencyList_v2::end () const -> const_iterator +auto FrequencyList_v2_101::end () const -> const_iterator { return const_iterator (this, rowCount ()); } -auto FrequencyList_v2::find (Frequency f) const -> const_iterator +auto FrequencyList_v2_101::find (Frequency f) const -> const_iterator { int row {0}; for (; row < rowCount (); ++row) @@ -950,7 +1290,7 @@ auto FrequencyList_v2::find (Frequency f) const -> const_iterator return const_iterator (this, row); } -auto FrequencyList_v2::filtered_bands () const -> BandSet +auto FrequencyList_v2_101::filtered_bands () const -> BandSet { BandSet result; for (auto const& item : *this) @@ -960,7 +1300,7 @@ auto FrequencyList_v2::filtered_bands () const -> BandSet return result; } -auto FrequencyList_v2::all_bands (Region region, Mode mode) const -> BandSet +auto FrequencyList_v2_101::all_bands (Region region, Mode mode) const -> BandSet { BandSet result; for (auto const& item : m_->frequency_list_) @@ -977,6 +1317,93 @@ auto FrequencyList_v2::all_bands (Region region, Mode mode) const -> BandSet return result; } +FrequencyList_v2_101::FrequencyItems FrequencyList_v2_101::from_json_file(QFile *input_file) +{ + FrequencyList_v2_101::FrequencyItems list; + QJsonDocument doc = QJsonDocument::fromJson(input_file->readAll()); + if (doc.isNull()) + { + throw ReadFileException {tr ("Failed to parse JSON file")}; + } + QJsonObject obj = doc.object(); + if (obj.isEmpty()) + { + throw ReadFileException{tr("Information Missing")}; + } + QJsonArray arr = obj["frequencies"].toArray(); + if (arr.isEmpty()) + { + throw ReadFileException{tr ("No Frequencies were found")}; + } + int valid_entry_count = 0; + int skipped_entry_count = 0; + for (auto const &item: arr) + { + QString mode_s, region_s; + QJsonObject obj = item.toObject(); + FrequencyList_v2_101::Item freq; + region_s = obj["region"].toString(); + mode_s = obj["mode"].toString(); + + freq.frequency_ = obj["frequency"].toString().toDouble() * 1e6; + freq.region_ = IARURegions::value(region_s); + freq.mode_ = Modes::value(mode_s); + freq.description_ = obj["description"].toString(); + freq.source_ = obj["source"].toString(); + freq.start_time_ = QDateTime::fromString(obj["start_time"].toString(), Qt::ISODate); + freq.end_time_ = QDateTime::fromString(obj["end_time"].toString(), Qt::ISODate); + freq.preferred_ = obj["preferred"].toBool(); + + if ((freq.mode_ != Modes::ALL || QString::compare("ALL", mode_s)) && + (freq.region_ != IARURegions::ALL || QString::compare("ALL", region_s, Qt::CaseInsensitive)) && + freq.isSane()) + { + list.push_back(freq); + valid_entry_count++; + } else + skipped_entry_count++; + } + //MessageBox::information_message(this, tr("Loaded Frequencies from %1").arg(file_name), + // tr("Entries Valid/Skipped %1").arg(QString::number(valid_entry_count) + "/" + + // QString::number(skipped_entry_count))); + return list; +} +// write JSON format to a file +void FrequencyList_v2_101::to_json_file(QFile *output_file, QString magic_s, QString version_s, + FrequencyItems const &frequency_items) +{ + QJsonObject jobject{ + {"wsjtx_file", "qrg"}, + {"wsjtx_version", QCoreApplication::applicationVersion()+" "+revision()}, + {"generated_at", QDateTime::currentDateTimeUtc ().toString (Qt::ISODate)}, + {"wsjtx_filetype", magic_s}, + {"qrg_version", version_s}, + {"frequency_count", frequency_items.count()}}; + + QJsonArray array; + for (auto &item: frequency_items) + array.append(item.toJson()); + jobject["frequencies"] = array; + + QJsonDocument d = QJsonDocument(jobject); + output_file->write(d.toJson()); +} + +// previous version 100 of the FrequencyList_v2 class +QDataStream& operator >> (QDataStream& is, FrequencyList_v2::Item& item) +{ + return is >> item.frequency_ + >> item.mode_ + >> item.region_; +} + +QDataStream& operator << (QDataStream& os, FrequencyList_v2::Item const& item) +{ + return os << item.frequency_ + << item.mode_ + << item.region_; +} + // // Obsolete version of FrequencyList no longer used but needed to // allow loading and saving of old settings contents without damage diff --git a/models/FrequencyList.hpp b/models/FrequencyList.hpp index a3b9c117a..04b51a863 100644 --- a/models/FrequencyList.hpp +++ b/models/FrequencyList.hpp @@ -5,6 +5,11 @@ #include #include +#include +#include +#include +#include +#include #include "Radio.hpp" #include "IARURegions.hpp" @@ -37,7 +42,7 @@ class Bands; // Implements the QSortFilterProxyModel interface for a list of spot // frequencies. // -class FrequencyList_v2 final +class FrequencyList_v2_101 final : public QSortFilterProxyModel { Q_OBJECT; @@ -52,18 +57,26 @@ public: Frequency frequency_; Mode mode_; Region region_; + QString description_; + QString source_; + QDateTime start_time_; + QDateTime end_time_; + bool preferred_; // preferred frequency for this band and mode QString toString () const; + bool isSane() const; + QJsonObject toJson () const; + }; using FrequencyItems = QList; using BandSet = QSet; - enum Column {region_column, mode_column, frequency_column, frequency_mhz_column, SENTINAL}; + enum Column {region_column, mode_column, frequency_column, frequency_mhz_column, description_column, start_time_column, end_time_column, source_column, preferred_column, SENTINAL}; // an iterator that meets the requirements of the C++ for range statement class const_iterator { public: - const_iterator (FrequencyList_v2 const * parent, int row) + const_iterator (FrequencyList_v2_101 const * parent, int row) : parent_ {parent} , row_ {row} { @@ -76,18 +89,21 @@ public: const_iterator& operator ++ (); private: - FrequencyList_v2 const * parent_; + FrequencyList_v2_101 const * parent_; int row_; }; - explicit FrequencyList_v2 (Bands const *, QObject * parent = nullptr); - ~FrequencyList_v2 (); + explicit FrequencyList_v2_101 (Bands const *, QObject * parent = nullptr); + + ~FrequencyList_v2_101 (); // Load and store underlying items FrequencyItems frequency_list (FrequencyItems); FrequencyItems const& frequency_list () const; FrequencyItems frequency_list (QModelIndexList const&) const; void frequency_list_merge (FrequencyItems const&); + void to_json_file(QFile *, QString, QString, FrequencyItems const&); + static FrequencyItems from_json_file(QFile *); // Iterators for the sorted and filtered items // @@ -116,7 +132,7 @@ public: int best_working_frequency (QString const& band) const; // Set filter - Q_SLOT void filter (Region, Mode); + Q_SLOT void filter (Region, Mode, bool); // Reset Q_SLOT void reset_to_defaults (); @@ -129,6 +145,9 @@ public: // Proxy API bool filterAcceptsRow (int source_row, QModelIndex const& parent) const override; + // Refresh the filter based on the current filter settings (underlying data may have changed) + void filter_refresh (); + // Custom roles. static int constexpr SortRole = Qt::UserRole; @@ -138,25 +157,56 @@ private: }; inline -bool operator == (FrequencyList_v2::Item const& lhs, FrequencyList_v2::Item const& rhs) +bool operator == (FrequencyList_v2_101::Item const& lhs, FrequencyList_v2_101::Item const& rhs) { return lhs.frequency_ == rhs.frequency_ && lhs.region_ == rhs.region_ - && lhs.mode_ == rhs.mode_; + && lhs.mode_ == rhs.mode_ + && lhs.description_ == rhs.description_ + && lhs.source_ == rhs.source_ + && lhs.start_time_ == rhs.start_time_ + && lhs.end_time_ == rhs.end_time_ + && lhs.preferred_ == rhs.preferred_; } +QDataStream& operator << (QDataStream&, FrequencyList_v2_101::Item const&); +QDataStream& operator >> (QDataStream&, FrequencyList_v2_101::Item&); + +#if !defined (QT_NO_DEBUG_STREAM) +QDebug operator << (QDebug, FrequencyList_v2_101::Item const&); +#endif + +Q_DECLARE_METATYPE (FrequencyList_v2_101::Item); +Q_DECLARE_METATYPE (FrequencyList_v2_101::FrequencyItems); + +class FrequencyList_v2 final +{ + +public: + using Frequency = Radio::Frequency; + using Mode = Modes::Mode; + using Region = IARURegions::Region; + + struct Item + { + Frequency frequency_; + Mode mode_; + Region region_; + QString toString () const; + }; + using FrequencyItems = QList; + +private: + FrequencyItems frequency_list_; +}; + QDataStream& operator << (QDataStream&, FrequencyList_v2::Item const&); QDataStream& operator >> (QDataStream&, FrequencyList_v2::Item&); -#if !defined (QT_NO_DEBUG_STREAM) -QDebug operator << (QDebug, FrequencyList_v2::Item const&); -#endif - Q_DECLARE_METATYPE (FrequencyList_v2::Item); Q_DECLARE_METATYPE (FrequencyList_v2::FrequencyItems); - // // Obsolete version of FrequencyList no longer used but needed to // allow loading and saving of old settings contents without damage @@ -184,4 +234,18 @@ QDataStream& operator >> (QDataStream&, FrequencyList::Item&); Q_DECLARE_METATYPE (FrequencyList::Item); Q_DECLARE_METATYPE (FrequencyList::FrequencyItems); +class ReadFileException : public QException { +public: + ReadFileException (QString const& message) + : message_ {message} + { + } + + void raise () const override { throw *this; } + ReadFileException * clone () const override { return new ReadFileException {*this}; } + + QString filename_; + QString message_; +}; + #endif diff --git a/models/StationList.cpp b/models/StationList.cpp index ff8d153f0..2b5012597 100644 --- a/models/StationList.cpp +++ b/models/StationList.cpp @@ -527,7 +527,7 @@ bool StationList::impl::dropMimeData (QMimeData const * data, Qt::DropAction act QDataStream stream {&encoded_data, QIODevice::ReadOnly}; while (!stream.atEnd ()) { - FrequencyList_v2::Item item; + FrequencyList_v2_101::Item item; stream >> item; auto const& band = bands_->find (item.frequency_); if (stations_.cend () == std::find_if (stations_.cbegin () diff --git a/translations/wsjtx_ca.ts b/translations/wsjtx_ca.ts index 287a45a87..db7c4fe94 100644 --- a/translations/wsjtx_ca.ts +++ b/translations/wsjtx_ca.ts @@ -1477,7 +1477,7 @@ Error: %2 - %3 - FrequencyList_v2 + FrequencyList_v2_100 diff --git a/translations/wsjtx_es.ts b/translations/wsjtx_es.ts index 3a750b2cf..e2cc8caf7 100644 --- a/translations/wsjtx_es.ts +++ b/translations/wsjtx_es.ts @@ -1630,7 +1630,7 @@ Error: %2 - %3 - FrequencyList_v2 + FrequencyList_v2_100 diff --git a/translations/wsjtx_zh_HK.ts b/translations/wsjtx_zh_HK.ts index fc16afbdc..86f599a31 100644 --- a/translations/wsjtx_zh_HK.ts +++ b/translations/wsjtx_zh_HK.ts @@ -1475,7 +1475,7 @@ Error: %2 - %3 - FrequencyList_v2 + FrequencyList_v2_100 diff --git a/translations/wsjtx_zh_TW.ts b/translations/wsjtx_zh_TW.ts index 15c9131de..a4b23d7d3 100644 --- a/translations/wsjtx_zh_TW.ts +++ b/translations/wsjtx_zh_TW.ts @@ -1475,7 +1475,7 @@ Error: %2 - %3 - FrequencyList_v2 + FrequencyList_v2_100 diff --git a/validators/LiveFrequencyValidator.cpp b/validators/LiveFrequencyValidator.cpp index 873224920..70c992a9d 100644 --- a/validators/LiveFrequencyValidator.cpp +++ b/validators/LiveFrequencyValidator.cpp @@ -12,7 +12,7 @@ LiveFrequencyValidator::LiveFrequencyValidator (QComboBox * combo_box , Bands const * bands - , FrequencyList_v2 const * frequencies + , FrequencyList_v2_101 const * frequencies , Frequency const * nominal_frequency , QWidget * parent) : QRegExpValidator { diff --git a/validators/LiveFrequencyValidator.hpp b/validators/LiveFrequencyValidator.hpp index 823f8f745..4224d89a4 100644 --- a/validators/LiveFrequencyValidator.hpp +++ b/validators/LiveFrequencyValidator.hpp @@ -7,7 +7,7 @@ #include "Radio.hpp" class Bands; -class FrequencyList_v2; +class FrequencyList_v2_101; class QComboBox; class QWidget; @@ -35,7 +35,7 @@ public: LiveFrequencyValidator (QComboBox * combo_box // associated combo box , Bands const * bands // bands model - , FrequencyList_v2 const * frequencies // working frequencies model + , FrequencyList_v2_101 const * frequencies // working frequencies model , Frequency const * nominal_frequency , QWidget * parent = nullptr); @@ -46,7 +46,7 @@ public: private: Bands const * bands_; - FrequencyList_v2 const * frequencies_; + FrequencyList_v2_101 const * frequencies_; Frequency const * nominal_frequency_; QComboBox * combo_box_; }; diff --git a/widgets/BandComboBox.cpp b/widgets/BandComboBox.cpp index 0dc8eba61..7dfb77e08 100644 --- a/widgets/BandComboBox.cpp +++ b/widgets/BandComboBox.cpp @@ -15,7 +15,7 @@ BandComboBox::BandComboBox (QWidget * parent) // item text. void BandComboBox::showPopup () { - auto minimum_width = view ()->sizeHintForColumn (FrequencyList_v2::frequency_mhz_column); + auto minimum_width = view ()->sizeHintForColumn (FrequencyList_v2_101::frequency_mhz_column); if (count () > maxVisibleItems ()) { // for some as yet unknown reason, in FT8 mode the scrollbar diff --git a/widgets/displaytext.cpp b/widgets/displaytext.cpp index 65fc1d40b..bdbad0e69 100644 --- a/widgets/displaytext.cpp +++ b/widgets/displaytext.cpp @@ -86,7 +86,7 @@ void DisplayText::mouseDoubleClickEvent(QMouseEvent *e) void DisplayText::insertLineSpacer(QString const& line) { - appendText (line, "#d3d3d3"); + insertText (line, "#d3d3d3"); } namespace @@ -123,11 +123,11 @@ namespace } } -void DisplayText::appendText(QString const& text, QColor bg, QColor fg - , QString const& call1, QString const& call2) +void DisplayText::insertText(QString const& text, QColor bg, QColor fg + , QString const& call1, QString const& call2, QTextCursor::MoveOperation location) { auto cursor = textCursor (); - cursor.movePosition (QTextCursor::End); + cursor.movePosition (location); auto block_format = cursor.blockFormat (); auto format = cursor.blockCharFormat (); format.setFont (char_font_); @@ -484,7 +484,7 @@ void DisplayText::displayDecodedText(DecodedText const& decodedText, QString con } } - appendText (message.trimmed (), bg, fg, decodedText.call (), dxCall); + insertText (message.trimmed (), bg, fg, decodedText.call (), dxCall); } @@ -516,18 +516,19 @@ void DisplayText::displayTransmittedText(QString text, QString modeTx, qint32 tx QColor fg; highlight_types types {Highlight::Tx}; set_colours (m_config, &bg, &fg, types); - appendText (t, bg, fg); + insertText (t, bg, fg); } void DisplayText::displayQSY(QString text) { QString t = QDateTime::currentDateTimeUtc().toString("hhmmss") + " " + text; - appendText (t, "hotpink"); + insertText (t, "hotpink"); } -void DisplayText::displayFoxToBeCalled(QString t, QColor bg, QColor fg) +void DisplayText::displayHoundToBeCalled(QString t, bool bAtTop, QColor bg, QColor fg) { - appendText (t, bg, fg); + if (bAtTop) t = t + "\n"; // need a newline when insertion at top + insertText(t, bg, fg, "", "", bAtTop ? QTextCursor::Start : QTextCursor::End); } namespace diff --git a/widgets/displaytext.h b/widgets/displaytext.h index 5810972cd..b0db7ca8b 100644 --- a/widgets/displaytext.h +++ b/widgets/displaytext.h @@ -33,7 +33,7 @@ public: bool haveFSpread = false, float fSpread = 0.0, bool bDisplayPoints=false, int points=-99); void displayTransmittedText(QString text, QString modeTx, qint32 txFreq, bool bFastMode, double TRperiod); void displayQSY(QString text); - void displayFoxToBeCalled(QString t, QColor bg = QColor {}, QColor fg = QColor {}); + void displayHoundToBeCalled(QString t, bool bAtTop=false, QColor bg = QColor {}, QColor fg = QColor {}); void new_period (); QString CQPriority(){return m_CQPriority;}; qint32 m_points; @@ -42,8 +42,8 @@ public: Q_SIGNAL void selectCallsign (Qt::KeyboardModifiers); Q_SIGNAL void erased (); - Q_SLOT void appendText (QString const& text, QColor bg = QColor {}, QColor fg = QColor {} - , QString const& call1 = QString {}, QString const& call2 = QString {}); + Q_SLOT void insertText (QString const& text, QColor bg = QColor {}, QColor fg = QColor {} + , QString const& call1 = QString {}, QString const& call2 = QString {}, QTextCursor::MoveOperation location=QTextCursor::End); Q_SLOT void erase (); Q_SLOT void highlight_callsign (QString const& callsign, QColor const& bg, QColor const& fg, bool last_period_only); @@ -62,6 +62,7 @@ private: , QString const& currentMode, QString extra); QFont char_font_; QAction * erase_action_; + QHash> highlighted_calls_; bool high_volume_; QMetaObject::Connection vertical_scroll_connection_; diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 17acca44c..6910cbea8 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -657,7 +657,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect(txMsgButtonGroup,SIGNAL(buttonClicked(int)),SLOT(set_ntx(int))); connect (ui->decodedTextBrowser, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnCall2); connect (ui->decodedTextBrowser2, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnCall); - connect (ui->textBrowser4, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnFoxQueue); + connect (ui->houndQueueTextBrowser, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnFoxQueue); + connect (ui->foxTxListTextBrowser, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnFoxInProgress); connect (ui->decodedTextBrowser, &DisplayText::erased, this, &MainWindow::band_activity_cleared); connect (ui->decodedTextBrowser2, &DisplayText::erased, this, &MainWindow::rx_frequency_activity_cleared); @@ -768,7 +769,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, // Hook up working frequencies. ui->bandComboBox->setModel (m_config.frequencies ()); - ui->bandComboBox->setModelColumn (FrequencyList_v2::frequency_mhz_column); + ui->bandComboBox->setModelColumn (FrequencyList_v2_101::frequency_mhz_column); // Enable live band combo box entry validation and action. auto band_validator = new LiveFrequencyValidator {ui->bandComboBox @@ -1020,6 +1021,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, m_wideGraph->setMode(m_mode); connect (&minuteTimer, &QTimer::timeout, this, &MainWindow::on_the_minute); + connect (&minuteTimer, &QTimer::timeout, this, &MainWindow::invalidate_frequencies_filter); + minuteTimer.setSingleShot (true); minuteTimer.start (ms_minute_error () + 60 * 1000); @@ -1046,9 +1049,9 @@ void MainWindow::not_GA_warning_message () MessageBox::critical_message (this, "This is a pre-release version of WSJT-X 2.6.0 made\n" "available for testing purposes. By design it will\n" - "be nonfunctional after Dec 31, 2022."); + "be nonfunctional after Mar 31, 2023."); auto now = QDateTime::currentDateTimeUtc (); - if (now >= QDateTime {{2022, 12, 31}, {23, 59, 59, 999}, Qt::UTC}) { + if (now >= QDateTime {{2023, 03, 31}, {23, 59, 59, 999}, Qt::UTC}) { Q_EMIT finished (); } } @@ -1064,6 +1067,14 @@ void MainWindow::splash_done () m_splash && m_splash->close (); } +void MainWindow::invalidate_frequencies_filter () +{ + // every interval, invalidate the frequency filter, so that if any + // working frequency goes in/out of scope, we pick it up. + m_config.frequencies ()->filter_refresh (); + ui->bandComboBox->update (); +} + void MainWindow::on_the_minute () { if (minuteTimer.isSingleShot ()) @@ -1427,9 +1438,14 @@ void MainWindow::setDecodedTextFont (QFont const& font) { ui->decodedTextBrowser->setContentFont (font); ui->decodedTextBrowser2->setContentFont (font); - ui->textBrowser4->setContentFont(font); - ui->textBrowser4->displayFoxToBeCalled(" "); - ui->textBrowser4->setText(""); + ui->houndQueueTextBrowser->setContentFont(font); + ui->houndQueueTextBrowser->displayHoundToBeCalled(" "); + ui->houndQueueTextBrowser->setText(""); + + ui->foxTxListTextBrowser->setContentFont(font); + ui->foxTxListTextBrowser->displayHoundToBeCalled(" "); + ui->foxTxListTextBrowser->setText(""); + auto style_sheet = "QLabel {" + font_as_stylesheet (font) + '}'; ui->lh_decodes_headings_label->setStyleSheet (ui->lh_decodes_headings_label->styleSheet () + style_sheet); ui->rh_decodes_headings_label->setStyleSheet (ui->rh_decodes_headings_label->styleSheet () + style_sheet); @@ -1655,7 +1671,7 @@ void MainWindow::dataSink(qint64 frames) t = t.asprintf("%9.6f %5.2f %7d %7.1f %7d %7d %7d %7.1f %7.1f",hour,xlevel, nDopTotal,width,echocom_.nsum,nqual,qRound(dfreq),sigdb,dBerr); t = t0 + t; - if (ui) ui->decodedTextBrowser->appendText(t); + if (ui) ui->decodedTextBrowser->insertText(t); t=t1+t; write_all("Rx",t); } @@ -2095,7 +2111,6 @@ void MainWindow::auto_tx_mode (bool state) void MainWindow::keyPressEvent (QKeyEvent * e) { - if(SpecOp::FOX == m_specOp) { switch (e->key()) { case Qt::Key_Return: @@ -2107,6 +2122,13 @@ void MainWindow::keyPressEvent (QKeyEvent * e) case Qt::Key_Backspace: qDebug() << "Key Backspace"; return; +#ifdef DEBUG_FOX + case Qt::Key_X: + if(e->modifiers() & Qt::AltModifier) { + foxTest(); + return; + } +#endif } QMainWindow::keyPressEvent (e); } @@ -3873,8 +3895,13 @@ void MainWindow::readFromStdout() //readFromStdout bool bProcessMsgNormally=ui->respondComboBox->currentText()=="CQ: First" or (ui->respondComboBox->currentText()=="CQ: Max Dist" and m_ActiveStationsWidget==NULL) or (m_ActiveStationsWidget!=NULL and !m_ActiveStationsWidget->isVisible()); - QString t=decodedtext.messageWords()[4]; - if(t.contains("R+") or t.contains("R-") or t=="R" or t=="RRR" or t=="RR73") bProcessMsgNormally=true; + if (decodedtext.messageWords().length() >= 2) { + QString t=decodedtext.messageWords()[2]; + if(t.contains("R+") or t.contains("R-") or t=="R" or t=="RRR" or t=="RR73") bProcessMsgNormally=true; + } + else { + bProcessMsgNormally=true; + } if(bProcessMsgNormally) { m_bDoubleClicked=true; m_bAutoReply = true; @@ -3885,6 +3912,9 @@ void MainWindow::readFromStdout() //readFromStdout QString deCall; QString deGrid; decodedtext.deCallAndGrid(/*out*/deCall,deGrid); + // if they dont' send their grid we'll use ours and assume dx=0 + if (deGrid.length() == 0) deGrid = m_config.my_grid(); + if(deGrid.contains(grid_regexp) or (deGrid.contains("+") or deGrid.contains("-"))) { int points=0; @@ -4885,7 +4915,7 @@ void MainWindow::startTx2() t = " Transmitting " + m_mode + " ----------------------- " + m_config.bands ()->find (m_freqNominal); t=beacon_start_time (m_TRperiod / 2) + ' ' + t.rightJustified (66, '-'); - ui->decodedTextBrowser->appendText(t); + ui->decodedTextBrowser->insertText(t); } write_all("Tx",m_currentMessage); } @@ -5164,7 +5194,7 @@ void MainWindow::doubleClickOnCall(Qt::KeyboardModifiers modifiers) if(SpecOp::FOX==m_specOp and m_decodedText2) { if(m_houndQueue.count()<10 and m_nSortedHounds>0) { QString t=cursor.block().text(); - selectHound(t); + selectHound(t, modifiers==(Qt::AltModifier)); // alt double-click gets put at top of queue } return; } @@ -7196,7 +7226,7 @@ void MainWindow::on_actionFreqCal_triggered() void MainWindow::switch_mode (Mode mode) { m_fastGraph->setMode(m_mode); - m_config.frequencies ()->filter (m_config.region (), mode); + m_config.frequencies ()->filter (m_config.region (), mode, true); // filter on current time auto const& row = m_config.frequencies ()->best_working_frequency (m_freqNominal); ui->bandComboBox->setCurrentIndex (row); if (row >= 0) { @@ -7441,7 +7471,7 @@ void MainWindow::on_actionOpen_log_directory_triggered () void MainWindow::on_bandComboBox_currentIndexChanged (int index) { auto const& frequencies = m_config.frequencies (); - auto const& source_index = frequencies->mapToSource (frequencies->index (index, FrequencyList_v2::frequency_column)); + auto const& source_index = frequencies->mapToSource (frequencies->index (index, FrequencyList_v2_101::frequency_column)); Frequency frequency {m_freqNominal}; if (source_index.isValid ()) { @@ -7469,7 +7499,7 @@ void MainWindow::on_bandComboBox_editTextChanged (QString const& text) void MainWindow::on_bandComboBox_activated (int index) { auto const& frequencies = m_config.frequencies (); - auto const& source_index = frequencies->mapToSource (frequencies->index (index, FrequencyList_v2::frequency_column)); + auto const& source_index = frequencies->mapToSource (frequencies->index (index, FrequencyList_v2_101::frequency_column)); Frequency frequency {m_freqNominal}; if (source_index.isValid ()) { @@ -7484,7 +7514,7 @@ void MainWindow::band_changed (Frequency f) { // Don't allow a7 decodes during the first period because they can be leftovers from the previous band no_a7_decodes = true; - QTimer::singleShot ((int(1000.0*m_TRperiod)), [=] {no_a7_decodes = false;}); + QTimer::singleShot ((int(1500.0*m_TRperiod)), [=] {no_a7_decodes = false;}); // Set the attenuation value if options are checked if (m_config.pwrBandTxMemory() && !m_tune) { @@ -8506,7 +8536,7 @@ void MainWindow::p1ReadFromStdout() //p1readFromStdout t = " " + tr ("Receiving") + " " + m_mode + " ----------------------- " + m_config.bands ()->find (m_dialFreqRxWSPR); t=beacon_start_time (-m_TRperiod / 2) + ' ' + t.rightJustified (66, '-'); - ui->decodedTextBrowser->appendText(t); + ui->decodedTextBrowser->insertText(t); } killFileTimer.start (45*1000); //Kill in 45s (for slow modes) } @@ -8580,12 +8610,12 @@ void MainWindow::p1ReadFromStdout() //p1readFromStdout QString band; Frequency f=1000000.0*rxFields.at(3).toDouble()+0.5; band = ' ' + m_config.bands ()->find (f); - ui->decodedTextBrowser->appendText(band.rightJustified (71, '-')); + ui->decodedTextBrowser->insertText(band.rightJustified (71, '-')); } m_tBlankLine = rxLine.left(4); } m_nWSPRdecodes += 1; - ui->decodedTextBrowser->appendText(rxLine); + ui->decodedTextBrowser->insertText(rxLine); } } } @@ -9168,7 +9198,9 @@ void MainWindow::on_pbFoxReset_clicked() QFile f(m_config.temp_dir().absoluteFilePath("houndcallers.txt")); f.remove(); ui->decodedTextBrowser->setText(""); - ui->textBrowser4->setText(""); + ui->houndQueueTextBrowser->setText(""); + ui->foxTxListTextBrowser->setText(""); + m_houndQueue.clear(); m_foxQSO.clear(); m_foxQSOinProgress.clear(); @@ -9288,19 +9320,18 @@ QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB) } //------------------------------------------------------------------------------ -void MainWindow::selectHound(QString line) +void MainWindow::selectHound(QString line, bool bTopQueue) { /* Called from doubleClickOnCall() in DXpedition Fox mode. * QString "line" is a user-selected line from left text window. * The line may be selected by double-clicking; alternatively, hitting * is equivalent to double-clicking on the top-most line. */ - if(line.length()==0) return; QString houndCall=line.split(" ",SkipEmptyParts).at(0); // Don't add a call already enqueued or in QSO - if(ui->textBrowser4->toPlainText().indexOf(houndCall) >= 0) return; + if(ui->houndQueueTextBrowser->toPlainText().indexOf(houndCall) >= 0) return; QString houndGrid=line.split(" ",SkipEmptyParts).at(1); // Hound caller's grid QString rpt=line.split(" ",SkipEmptyParts).at(2); // Hound SNR @@ -9310,16 +9341,25 @@ void MainWindow::selectHound(QString line) ui->decodedTextBrowser->setText(m_houndCallers); // Populate left window with Hound callers QString t1=houndCall + " "; QString t2=rpt; + QString t1_with_grid; if(rpt.mid(0,1) != "-" and rpt.mid(0,1) != "+") t2="+" + rpt; if(t2.length()==2) t2=t2.mid(0,1) + "0" + t2.mid(1,1); t1=t1.mid(0,12) + t2; - ui->textBrowser4->displayFoxToBeCalled(t1); // Add hound call and rpt to tb4 - t1=t1 + " " + houndGrid; // Append the grid - m_houndQueue.enqueue(t1); // Put this hound into the queue - writeFoxQSO(" Sel: " + t1); - QTextCursor cursor = ui->textBrowser4->textCursor(); + ui->houndQueueTextBrowser->displayHoundToBeCalled(t1, bTopQueue); // Add hound call and rpt to tb4 + t1_with_grid=t1 + " " + houndGrid; // Append the grid + + if (bTopQueue) + { + m_houndQueue.prepend(t1_with_grid); // Put this hound into the queue at the top + } + else + { + m_houndQueue.enqueue(t1_with_grid); // Put this hound into the queue + } + writeFoxQSO(" Sel: " + t1_with_grid); + QTextCursor cursor = ui->houndQueueTextBrowser->textCursor(); cursor.setPosition(0); // Scroll to top of list - ui->textBrowser4->setTextCursor(cursor); + ui->houndQueueTextBrowser->setTextCursor(cursor); } //------------------------------------------------------------------------------ @@ -9348,7 +9388,7 @@ void MainWindow::houndCallers() houndCall=line.mid(0,i0); paddedHoundCall=houndCall + " "; //Don't list a hound already in the queue - if(!ui->textBrowser4->toPlainText().contains(paddedHoundCall)) { + if(!ui->houndQueueTextBrowser->toPlainText().contains(paddedHoundCall)) { if(m_loggedByFox[houndCall].contains(m_lastBand)) continue; //already logged on this band if(m_foxQSO.contains(houndCall)) continue; //still in the QSO map auto const& entity = m_logBook.countries ()->lookup (houndCall); @@ -9405,6 +9445,19 @@ void MainWindow::foxRxSequencer(QString msg, QString houndCall, QString rptRcvd) } } } +void MainWindow::updateFoxQSOsInProgressDisplay() +{ + + ui->foxTxListTextBrowser->clear(); + for (int i = 0; i < m_foxQSOinProgress.count(); i++) + { + //First do those for QSOs in progress + QString hc = m_foxQSOinProgress.at(i); + QString status = m_foxQSO[hc].ncall > m_maxStrikes ? QString(" (rx) ") : QString(" "); + QString str = (hc + " ").left(13) + QString::number(m_foxQSO[hc].ncall) + status; + ui->foxTxListTextBrowser->displayHoundToBeCalled(str); + } +} void MainWindow::foxTxSequencer() { @@ -9601,13 +9654,14 @@ Transmit: m_foxLogWindow->rate (m_foxRateQueue.size ()); m_foxLogWindow->queued (m_foxQSOinProgress.count ()); } + updateFoxQSOsInProgressDisplay(); } void MainWindow::rm_tb4(QString houndCall) { if(houndCall=="") return; QString t=""; - QString tb4=ui->textBrowser4->toPlainText(); + QString tb4=ui->houndQueueTextBrowser->toPlainText(); QStringList list=tb4.split("\n"); int n=list.size(); int j=0; @@ -9620,24 +9674,78 @@ void MainWindow::rm_tb4(QString houndCall) } } t.replace("\n\n","\n"); - ui->textBrowser4->setText(t); + ui->houndQueueTextBrowser->setText(t); } void MainWindow::doubleClickOnFoxQueue(Qt::KeyboardModifiers modifiers) { if(modifiers==9999) return; //Silence compiler warning - QTextCursor cursor=ui->textBrowser4->textCursor(); + QTextCursor cursor=ui->houndQueueTextBrowser->textCursor(); cursor.setPosition(cursor.selectionStart()); - QString houndCall=cursor.block().text().mid(0,12).trimmed(); - rm_tb4(houndCall); - writeFoxQSO(" Del: " + houndCall); - QQueue tmpQueue; - while(!m_houndQueue.isEmpty()) { - QString t=m_houndQueue.dequeue(); - QString hc=t.mid(0,12).trimmed(); - if(hc != houndCall) tmpQueue.enqueue(t); - } - m_houndQueue.swap(tmpQueue); + QString houndLine=cursor.block().text(); + QString houndCall=houndLine.mid(0,12).trimmed(); + + if (modifiers == (Qt::AltModifier)) + { + //Alt-click on a Fox queue entry - put on top of queue + // remove + for(auto i=0; ihoundQueueTextBrowser->clear(); + for (QString line: m_houndQueue) + { + ui->houndQueueTextBrowser->displayHoundToBeCalled(line.mid(0,16), false); + } + } else + { + rm_tb4(houndCall); + writeFoxQSO(" Del: " + houndCall); + QQueue tmpQueue; + while (!m_houndQueue.isEmpty()) + { + QString t = m_houndQueue.dequeue(); + QString hc = t.mid(0, 12).trimmed(); + if (hc != houndCall) tmpQueue.enqueue(t); + } + m_houndQueue.swap(tmpQueue); + } +} + +void MainWindow::doubleClickOnFoxInProgress(Qt::KeyboardModifiers modifiers) +{ + if (modifiers == 9999) return; //Silence compiler warning + QTextCursor cursor = ui->foxTxListTextBrowser->textCursor(); + cursor.setPosition(cursor.selectionStart()); + QString houndLine = cursor.block().text(); + QString houndCall = houndLine.mid(0, 12).trimmed(); + + if (modifiers == 0) + { + m_foxQSO[houndCall].ncall = m_maxStrikes + 1; // time them out + updateFoxQSOsInProgressDisplay(); + } +} + +void MainWindow::foxQueueTopCallCommand() +{ + m_decodedText2 = true; + if(SpecOp::FOX==m_specOp && m_decodedText2 && m_houndQueue.count() < 10) + { + + QTextCursor cursor = ui->decodedTextBrowser->textCursor(); + cursor.setPosition(cursor.selectionStart()); + QString houndCallLine = cursor.block().text(); + + writeFoxQSO(" QTop: " + houndCallLine); + selectHound(houndCallLine, true); // alt double-click gets put at top of queue + } } void MainWindow::foxGenWaveform(int i,QString fm) @@ -9691,14 +9799,29 @@ void MainWindow::writeFoxQSO(QString const& msg) /*################################################################################### */ void MainWindow::foxTest() { - QFile f("steps.txt"); - if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) return; + QString curdir = QDir::currentPath(); + bool b_hounds_written = false; - QFile fdiag("diag.txt"); + QFile fdiag(m_config.writeable_data_dir ().absoluteFilePath("diag.txt")); if(!fdiag.open(QIODevice::WriteOnly | QIODevice::Text)) return; + QFile f(m_config.writeable_data_dir ().absoluteFilePath("steps.txt")); + if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + fdiag.write("Cannot open steps.txt"); + return; + } + QTextStream s(&f); QTextStream sdiag(&fdiag); + + QFile fhounds(m_config.temp_dir().absoluteFilePath("houndcallers.txt")); + if(!fhounds.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) + { + sdiag << "can't write to houndcallers.txt"; + return; + } + QTextStream houndstream(&fhounds); + QString line; QString t; QString msg; @@ -9716,7 +9839,19 @@ void MainWindow::foxTest() } if(line.contains("Sel:")) { t=line.mid(43,6) + " " + line.mid(54,4) + " " + line.mid(50,3); - selectHound(t); + selectHound(t, false); + } + auto line_trimmed = line.trimmed(); + if(line_trimmed.startsWith("Hound:")) { + t=line_trimmed.mid(6,-1).trimmed(); + b_hounds_written = true; + houndstream << t +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::endl +#else + << endl +#endif + ; } if(line.contains("Del:")) { @@ -9756,6 +9891,11 @@ void MainWindow::foxTest() sdiag << t << line.mid(37).trimmed() << "\n"; } } + if (b_hounds_written) + { + fhounds.close(); + houndCallers(); + } } void MainWindow::write_all(QString txRx, QString message) diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index d9e738920..a2558fe81 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -127,6 +127,7 @@ public slots: void doubleClickOnCall (Qt::KeyboardModifiers); void doubleClickOnCall2(Qt::KeyboardModifiers); void doubleClickOnFoxQueue(Qt::KeyboardModifiers); + void doubleClickOnFoxInProgress(Qt::KeyboardModifiers modifiers); void readFromStdout(); void p1ReadFromStdout(); void setXIT(int n, Frequency base = 0u); @@ -476,7 +477,7 @@ private: qint32 m_k0; qint32 m_kdone; qint32 m_nPick; - FrequencyList_v2::const_iterator m_frequency_list_fcal_iter; + FrequencyList_v2_101::const_iterator m_frequency_list_fcal_iter; qint32 m_nTx73; qint32 m_UTCdisk; qint32 m_wait; @@ -823,6 +824,7 @@ private: void subProcessError (QProcess *, QProcess::ProcessError); void statusUpdate () const; void update_watchdog_label (); + void invalidate_frequencies_filter (); void on_the_minute (); void add_child_to_event_filter (QObject *); void remove_child_from_event_filter (QObject *); @@ -832,8 +834,10 @@ private: void displayWidgets(qint64 n); QChar current_submode () const; // returns QChar {0} if submode is not appropriate void write_transmit_entry (QString const& file_name); - void selectHound(QString t); + void selectHound(QString t, bool bTopQueue); void houndCallers(); + void updateFoxQSOsInProgressDisplay(); + void foxQueueTopCallCommand(); void foxRxSequencer(QString msg, QString houndCall, QString rptRcvd); void foxTxSequencer(); void foxGenWaveform(int i,QString fm); diff --git a/widgets/mainwindow.ui b/widgets/mainwindow.ui index 733dc4dc9..07ea2fb0a 100644 --- a/widgets/mainwindow.ui +++ b/widgets/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 901 - 665 + 991 + 690 @@ -1018,7 +1018,7 @@ Yellow when too low 0 - + 0 @@ -1454,7 +1454,7 @@ When not checked you can view the calibration results. - 0 + 1 @@ -2005,10 +2005,31 @@ Double-click to reset to the standard 73 message 2 - - - - + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + 0 + + + 6 + + + 0 @@ -2021,21 +2042,62 @@ Double-click to reset to the standard 73 message 16777215 - - Max dB + + Random - - -15 + + 5 - - 70 + + + Random + + + + + Call + + + + + Grid + + + + + S/N (dB) + + + + + Distance + + + + + + + + + 9 + - - 30 + + Queue + + + Qt::AlignCenter - + + + + More CQs + + + + CQ @@ -2132,14 +2194,7 @@ Double-click to reset to the standard 73 message - - - - Reset - - - - + @@ -2167,7 +2222,35 @@ Double-click to reset to the standard 73 message - + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Max dB + + + -15 + + + 70 + + + 30 + + + + @@ -2201,54 +2284,24 @@ Double-click to reset to the standard 73 message - - - - - 0 - 0 - + + + + Reset - - - 16777215 - 16777215 - - - - Random - - - 5 - - - - Random - - - - - Call - - - - - Grid - - - - - S/N (dB) - - - - - Distance - - - + + + + In Progress + + + Qt::AlignCenter + + + + Qt::Vertical @@ -2261,34 +2314,49 @@ Double-click to reset to the standard 73 message - - - - More CQs + + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QAbstractScrollArea::AdjustToContentsOnFirstShow - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - QAbstractScrollArea::AdjustToContentsOnFirstShow - - - @@ -3076,7 +3144,7 @@ QPushButton[state="ok"] { 0 0 - 901 + 991 22 @@ -3836,14 +3904,6 @@ QPushButton[state="ok"] { tx6 txrb6 txb6 - textBrowser4 - comboBoxHoundSort - sbNlist - sbMax_dB - sbNslots - comboBoxCQ - cbMoreCQs - pbFoxReset WSPRfreqSpinBox sbFST4W_RxFreq sbFST4W_FTol