diff --git a/Bands.cpp b/Bands.cpp index d182eb1dd..ba0ed31f7 100644 --- a/Bands.cpp +++ b/Bands.cpp @@ -74,6 +74,19 @@ QString Bands::find (Frequency f) const return result; } +int Bands::find (QString const& band) const +{ + int result {-1}; + for (auto i = 0u; i < table_rows (); ++i) + { + if (band == ADIF_bands[i].name_) + { + result = i; + } + } + return result; +} + QString const& Bands::oob () { return oob_name; diff --git a/Bands.hpp b/Bands.hpp index adfe21309..ed625f2f1 100644 --- a/Bands.hpp +++ b/Bands.hpp @@ -55,6 +55,7 @@ public: // Model API // QString find (Frequency) const; // find band Frequency is in + int find (QString const&) const; // find row of band (-1 if not valid) static QString const& oob (); // Iterators diff --git a/FrequencyList.cpp b/FrequencyList.cpp index 339bbef66..d7043457b 100644 --- a/FrequencyList.cpp +++ b/FrequencyList.cpp @@ -583,16 +583,6 @@ auto FrequencyList::end () const -> FrequencyList::const_iterator return const_iterator (this, rowCount ()); } -auto FrequencyList::all_bands () const -> BandSet -{ - BandSet result; - for (auto const& item : m_->frequency_list_) - { - result << m_->bands_->find (item.frequency_); - } - return result; -} - auto FrequencyList::filtered_bands () const -> BandSet { BandSet result; @@ -602,3 +592,16 @@ auto FrequencyList::filtered_bands () const -> BandSet } return result; } + +auto FrequencyList::all_bands (Mode mode) const -> BandSet +{ + BandSet result; + for (auto const& item : m_->frequency_list_) + { + if (mode == Modes::NULL_MODE || item.mode_ == mode) + { + result << m_->bands_->find (item.frequency_); + } + } + return result; +} diff --git a/FrequencyList.hpp b/FrequencyList.hpp index c5c001a8d..382768857 100644 --- a/FrequencyList.hpp +++ b/FrequencyList.hpp @@ -90,7 +90,7 @@ public: const_iterator end () const; // Bands of the frequencies - BandSet all_bands () const; + BandSet all_bands (Mode = Modes::NULL_MODE) const; BandSet filtered_bands () const; // Find the row of the nearest best working frequency given a diff --git a/WSPRBandHopping.cpp b/WSPRBandHopping.cpp index 595e39214..27f8e3316 100644 --- a/WSPRBandHopping.cpp +++ b/WSPRBandHopping.cpp @@ -3,10 +3,12 @@ #include #include #include +#include #include #include "SettingsGroup.hpp" #include "Configuration.hpp" +#include "Bands.hpp" #include "FrequencyList.hpp" #include "WsprTxScheduler.h" #include "pimpl_impl.hpp" @@ -26,12 +28,11 @@ extern "C" namespace { - // These 10 bands are the hopping candidates and are globally coordinated - char const * const hopping_bands[] = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"}; - size_t constexpr num_bands {sizeof (hopping_bands) / sizeof (hopping_bands[0])}; + char const * const title = "WSPR Band Hopping"; char const * const periods[] = {"Sunrise grayline", "Day", "Sunset grayline", "Night", "Tune", "Rx only"}; size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])}; - char const * const title = "WSPR Band Hopping"; + // These 10 bands are globally coordinated + QList const coordinated_bands = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"}; } // @@ -41,9 +42,13 @@ class Dialog : public QDialog { public: - Dialog (QSettings *, QBitArray * bands, int * gray_line_duration, QWidget * parent = nullptr); + using BandList = QList; + + Dialog (QSettings *, Configuration const *, BandList const * WSPT_bands, QBitArray * bands + , int * gray_line_duration, QWidget * parent = nullptr); ~Dialog (); + Q_SLOT void frequencies_changed (); void resize_to_maximum (); private: @@ -51,48 +56,49 @@ private: void save_window_state (); QSettings * settings_; + Configuration const * configuration_; + BandList const * WSPR_bands_; QBitArray * bands_; int * gray_line_duration_; QPointer bands_table_; + QBrush coord_background_brush_; QPointer gray_line_width_spin_box_; + static int const band_index_role {Qt::UserRole}; }; -Dialog::Dialog (QSettings * settings, QBitArray * bands, int * gray_line_duration, QWidget * parent) +Dialog::Dialog (QSettings * settings, Configuration const * configuration, BandList const * WSPR_bands + , QBitArray * bands, int * gray_line_duration, QWidget * parent) : QDialog {parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint} , settings_ {settings} + , configuration_ {configuration} + , WSPR_bands_ {WSPR_bands} , bands_ {bands} , gray_line_duration_ {gray_line_duration} - , bands_table_ {new QTableWidget {num_periods, num_bands, this}} + , bands_table_ {new QTableWidget {this}} + , coord_background_brush_ {Qt::yellow} , gray_line_width_spin_box_ {new QSpinBox {this}} { + setWindowTitle (windowTitle () + ' ' + tr (title)); + { + SettingsGroup g {settings_, title}; + restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ()); + } + QVBoxLayout * main_layout {new QVBoxLayout}; - // set up and load the table of check boxes + bands_table_->setRowCount (num_periods); bands_table_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff); bands_table_->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff); - for (auto row = 0u; row < num_periods; ++row) - { - auto vertical_header = new QTableWidgetItem {periods[row]}; - vertical_header->setTextAlignment (Qt::AlignRight | Qt::AlignVCenter); - bands_table_->setVerticalHeaderItem (row, vertical_header); - for (auto column = 0u; column < num_bands; ++column) - { - if (0 == row) - { - auto horizontal_header = new QTableWidgetItem {hopping_bands[column]}; - bands_table_->setHorizontalHeaderItem (column, horizontal_header); - } - auto item = new QTableWidgetItem; - item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); - item->setCheckState (bands_[row].testBit (column) ? Qt::Checked : Qt::Unchecked); - bands_table_->setItem (row, column, item); - } - } - bands_table_->resizeColumnsToContents (); + bands_table_->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); + frequencies_changed (); main_layout->addWidget (bands_table_); + // recalculate table when frequencies change + connect (configuration_->frequencies (), &QAbstractItemModel::layoutChanged + , this, &Dialog::frequencies_changed); // handle changes by updating the underlying flags connect (bands_table_.data (), &QTableWidget::itemChanged, [this] (QTableWidgetItem * item) { - bands_[item->row ()].setBit (item->column (), Qt::Checked == item->checkState ()); + auto band_number = item->data (band_index_role).toInt (); + bands_[item->row ()].setBit (band_number, Qt::Checked == item->checkState ()); }); // set up the gray line duration spin box @@ -111,11 +117,6 @@ Dialog::Dialog (QSettings * settings, QBitArray * bands, int * gray_line_duratio main_layout->addLayout (bottom_layout); setLayout (main_layout); - setWindowTitle (windowTitle () + ' ' + tr (title)); - { - SettingsGroup g {settings_, title}; - restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ()); - } } Dialog::~Dialog () @@ -137,46 +138,89 @@ void Dialog::save_window_state () settings_->setValue ("geometry", saveGeometry ()); } +void Dialog::frequencies_changed () +{ + bands_table_->setColumnCount (WSPR_bands_->size ()); + // set up and load the table of check boxes + for (auto row = 0u; row < num_periods; ++row) + { + auto vertical_header = new QTableWidgetItem {periods[row]}; + vertical_header->setTextAlignment (Qt::AlignRight | Qt::AlignVCenter); + bands_table_->setVerticalHeaderItem (row, vertical_header); + int column {0}; + int band_number {0}; + for (auto const& band : *configuration_->bands ()) + { + if (WSPR_bands_->contains (band)) + { + if (0 == row) + { + auto horizontal_header = new QTableWidgetItem {band}; + bands_table_->setHorizontalHeaderItem (column, horizontal_header); + } + auto item = new QTableWidgetItem; + item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); + item->setCheckState (bands_[row].testBit (band_number) ? Qt::Checked : Qt::Unchecked); + item->setData (band_index_role, band_number); + if (coordinated_bands.contains (band)) + { + item->setBackground (coord_background_brush_); + } + bands_table_->setItem (row, column, item); + ++column; + } + ++band_number; + } + } + bands_table_->resizeColumnsToContents (); + auto is_visible = isVisible (); + show (); + resize_to_maximum (); + adjustSize (); // fix the size + if (!is_visible) + { + hide (); + } +} + // to get the dialog window exactly the right size to contain the // widgets without needing scroll bars we need to measure the size of // the table widget and set its minimum size to the measured size void Dialog::resize_to_maximum () { - int width {bands_table_->verticalHeader ()->width ()}; - int height {bands_table_->horizontalHeader ()->height ()}; - for (auto i = 0; i < bands_table_->columnCount (); ++i) - { - width += bands_table_->columnWidth (i); - } - for (auto i = 0; i < bands_table_->rowCount (); ++i) - { - height += bands_table_->rowHeight (i); - } - bands_table_->setMinimumSize ({width, height}); + bands_table_->setMinimumSize ({ + bands_table_->horizontalHeader ()->length () + + bands_table_->verticalHeader ()->width () + + 2 * bands_table_->frameWidth () + , bands_table_->verticalHeader ()->length () + + bands_table_->horizontalHeader ()->height () + + 2 * bands_table_->frameWidth () + }); + bands_table_->setMaximumSize (bands_table_->minimumSize ()); } class WSPRBandHopping::impl { public: + using BandList = Dialog::BandList; + impl (QSettings * settings, Configuration const * configuration, QWidget * parent_widget) : settings_ {settings} , configuration_ {configuration} , tx_percent_ {0} , parent_widget_ {parent_widget} - , bands_ { - QBitArray {num_bands}, - QBitArray {num_bands}, - QBitArray {num_bands}, - QBitArray {num_bands}, - QBitArray {num_bands}, - QBitArray {num_bands}, - } { + auto num_bands = configuration_->bands ()->rowCount (); + for (auto& flags : bands_) + { + flags.resize (num_bands); + } } QSettings * settings_; Configuration const * configuration_; int tx_percent_; + BandList WSPR_bands_; QWidget * parent_widget_; // 5 x 10 bit flags representing each hopping band in each period @@ -190,13 +234,23 @@ public: WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget) : m_ {settings, configuration, parent_widget} { + // detect changes to the working frequencies model + m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (Modes::WSPR).toList (); + connect (m_->configuration_->frequencies (), &QAbstractItemModel::layoutChanged + , [this] () { + m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (Modes::WSPR).toList (); + }); + // load settings SettingsGroup g {m_->settings_, title}; - auto size = m_->settings_->beginReadArray ("periods"); - for (auto i = 0; i < size; ++i) + size_t size = m_->settings_->beginReadArray ("phases"); + for (auto i = 0u; i < size; ++i) { - m_->settings_->setArrayIndex (i); - m_->bands_[i] = m_->settings_->value ("bands").toBitArray (); + if (i < num_periods) + { + m_->settings_->setArrayIndex (i); + m_->bands_[i] = m_->settings_->value ("bands").toBitArray (); + } } m_->settings_->endArray (); m_->gray_line_duration_ = m_->settings_->value ("GrayLineDuration", 60).toUInt (); @@ -206,7 +260,7 @@ WSPRBandHopping::~WSPRBandHopping () { // save settings SettingsGroup g {m_->settings_, title}; - m_->settings_->beginWriteArray ("periods"); + m_->settings_->beginWriteArray ("phases"); for (auto i = 0u; i < num_periods; ++i) { m_->settings_->setArrayIndex (i); @@ -221,13 +275,10 @@ void WSPRBandHopping::show_dialog (bool /* checked */) { if (!m_->dialog_) { - m_->dialog_ = new Dialog {m_->settings_, m_->bands_, &m_->gray_line_duration_, m_->parent_widget_}; + m_->dialog_ = new Dialog {m_->settings_, m_->configuration_, &m_->WSPR_bands_, m_->bands_ + , &m_->gray_line_duration_, m_->parent_widget_}; } m_->dialog_->show (); - m_->dialog_->resize_to_maximum (); - m_->dialog_->adjustSize (); // fix the size - m_->dialog_->setMinimumSize (m_->dialog_->size ()); - m_->dialog_->setMaximumSize (m_->dialog_->size ()); m_->dialog_->raise (); m_->dialog_->activateWindow (); } @@ -266,51 +317,81 @@ auto WSPRBandHopping::next_hop () -> Hop , &m_->gray_line_duration_, &period_index , my_grid.size ()); - // consult scheduler to determine if next period should be a tx interval - tx_next = next_tx_state(m_->tx_percent_); band_index = next_hopping_band(); if (100 == m_->tx_percent_) { tx_next = 1; } + else + { + // consult scheduler to determine if next period should be a tx interval + tx_next = next_tx_state(m_->tx_percent_); + } int frequencies_index {-1}; auto const& frequencies = m_->configuration_->frequencies (); - auto const& filtered_bands = frequencies->filtered_bands (); - if (m_->bands_[period_index].testBit (band_index) - && filtered_bands.contains (hopping_bands[band_index])) + auto const& bands = m_->configuration_->bands (); + auto const& band_name = bands->data (bands->index (band_index + 3, 0)).toString (); + if (m_->bands_[period_index].testBit (band_index + 3) // +3 for + // coordinated bands + && m_->WSPR_bands_.contains (band_name)) { // here we have a band that has been enabled in the hopping // matrix so check it it has a configured working frequency - frequencies_index = frequencies->best_working_frequency (hopping_bands[band_index]); - qDebug () << "scheduled:" << hopping_bands[band_index] << "frequency:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString (); + frequencies_index = frequencies->best_working_frequency (band_name); } // if we do not have a configured working frequency we next check // for a random selection from the other enabled bands in the - // hopping matrix + // bands matrix if (frequencies_index < 0) { - for (auto i = 0u; i < num_bands; ++i) + Dialog::BandList target_bands {m_->WSPR_bands_}; + // // remove all coordinated bands here since they are + // // scheduled above and including them in the random choice will + // // give them a biased weighting + // for (auto const& band : coordinated_bands) + // { + // target_bands.removeOne (band); + // } + + // remove bands that are not enabled for hopping + for (auto i = 0; i < m_->bands_[period_index].size (); ++i) { - int new_index = static_cast (qrand () % num_bands); // random choice - if (new_index != band_index && m_->bands_[period_index].testBit (new_index)) + if (!m_->bands_[period_index].testBit (i)) { - // here we have a random choice that is enabled in the - // hopping matrix and not the scheduled choice so we now - // check if it has a configured working frequency - frequencies_index = frequencies->best_working_frequency (hopping_bands[new_index]); - if (frequencies_index >= 0) + target_bands.removeOne (bands->data (bands->index (i, 0)).toString ()); + } + } + + auto num_bands = target_bands.size (); + if (num_bands) // we have some extra bands available + { + int target_index = static_cast (qrand () % num_bands); // random choice + // here we have a random choice that is enabled in the + // hopping matrix + frequencies_index = frequencies->best_working_frequency (target_bands[target_index]); + if (frequencies_index >= 0) + { + // we can use the random choice + qDebug () << "random:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString (); + band_index = bands->find (target_bands[target_index]); + if (band_index < 0) { - // we can use the random choice - qDebug () << "random:" << hopping_bands[new_index] << "frequency:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString (); - band_index = new_index; - break; + // this shouldn't happen + Q_ASSERT (band_index >= 0); + frequencies_index = -1; } } } } + else + { + band_index += 3; + qDebug () << "scheduled:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString (); + } + return { periods[period_index] diff --git a/WsprTxScheduler.cpp b/WsprTxScheduler.cpp index 0f0579900..61df3f360 100644 --- a/WsprTxScheduler.cpp +++ b/WsprTxScheduler.cpp @@ -93,6 +93,7 @@ void tx_print() } } printf("\n"); + fflush(stdout); } int create_tx_schedule(int pctx) diff --git a/mainwindow.cpp b/mainwindow.cpp index bdc3c1747..f2992ec41 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -4311,7 +4311,7 @@ void MainWindow::bandHopping() } if (ui->band_hopping_group_box->isChecked ()) { - QThread::msleep(500); //### Is this OK to do ??? ### + // QThread::msleep(500); //### Is this OK to do ??? ### if (hop_data.frequencies_index_ >= 0) { // new band ui->bandComboBox->setCurrentIndex (hop_data.frequencies_index_);