mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-01 08:07:10 -04:00
23d58d98a3
Provide a dialog for band hopping scheduling which replaces tab four. Ensure that split mode is not used for WSPR. Select the correct tab for WSPR operation. Shift WSPR band hopping code out of the MainWindow class. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@5517 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
302 lines
10 KiB
C++
302 lines
10 KiB
C++
#include "WSPRBandHopping.hpp"
|
|
|
|
#include <QPointer>
|
|
#include <QSettings>
|
|
#include <QBitArray>
|
|
#include <QtWidgets>
|
|
|
|
#include "SettingsGroup.hpp"
|
|
#include "Configuration.hpp"
|
|
#include "FrequencyList.hpp"
|
|
#include "pimpl_impl.hpp"
|
|
#include "moc_WSPRBandHopping.cpp"
|
|
|
|
extern "C"
|
|
{
|
|
#ifndef CMAKE_BUILD
|
|
#define FC_hopping hopping_
|
|
#else
|
|
#include "FC.h"
|
|
void FC_hopping (int const * year, int const * month, int const * nday, float const * uth, char const * my_grid
|
|
, int const * nduration, int const * npctx, int * isun, int * iband
|
|
, int * ntxnext, int my_grid_len);
|
|
#endif
|
|
};
|
|
|
|
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 periods[] = {"Sunrise grayline", "Day", "Sunset grayline", "Night", "Tune"};
|
|
size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])};
|
|
char const * const title = "WSPR Band Hopping";
|
|
}
|
|
|
|
//
|
|
// Dialog - maintenance of band hopping options
|
|
//
|
|
class Dialog
|
|
: public QDialog
|
|
{
|
|
public:
|
|
Dialog (QSettings *, QBitArray * bands, int * gray_line_duration, QWidget * parent = nullptr);
|
|
~Dialog ();
|
|
|
|
void resize_to_maximum ();
|
|
|
|
private:
|
|
void closeEvent (QCloseEvent *) override;
|
|
void save_window_state ();
|
|
|
|
QSettings * settings_;
|
|
QBitArray * bands_;
|
|
int * gray_line_duration_;
|
|
QPointer<QTableWidget> bands_table_;
|
|
QPointer<QSpinBox> gray_line_width_spin_box_;
|
|
};
|
|
|
|
Dialog::Dialog (QSettings * settings, QBitArray * bands, int * gray_line_duration, QWidget * parent)
|
|
: QDialog {parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint}
|
|
, settings_ {settings}
|
|
, bands_ {bands}
|
|
, gray_line_duration_ {gray_line_duration}
|
|
, bands_table_ {new QTableWidget {num_periods, num_bands, this}}
|
|
, gray_line_width_spin_box_ {new QSpinBox {this}}
|
|
{
|
|
QVBoxLayout * main_layout {new QVBoxLayout};
|
|
|
|
// set up and load the table of check boxes
|
|
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 ();
|
|
main_layout->addWidget (bands_table_);
|
|
// 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 ());
|
|
});
|
|
|
|
// set up the gray line duration spin box
|
|
gray_line_width_spin_box_->setRange (1, 60 * 2);
|
|
gray_line_width_spin_box_->setSuffix ("min");
|
|
gray_line_width_spin_box_->setValue (*gray_line_duration_);
|
|
QFormLayout * form_layout = new QFormLayout;
|
|
form_layout->addRow (tr ("Gray time:"), gray_line_width_spin_box_);
|
|
connect (gray_line_width_spin_box_.data ()
|
|
, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged)
|
|
, [this] (int new_value) {*gray_line_duration_ = new_value;});
|
|
|
|
QHBoxLayout * bottom_layout = new QHBoxLayout;
|
|
bottom_layout->addStretch ();
|
|
bottom_layout->addLayout (form_layout);
|
|
main_layout->addLayout (bottom_layout);
|
|
|
|
setLayout (main_layout);
|
|
setWindowTitle (windowTitle () + ' ' + tr (title));
|
|
{
|
|
SettingsGroup g {settings_, title};
|
|
restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
|
|
}
|
|
}
|
|
|
|
Dialog::~Dialog ()
|
|
{
|
|
// do this here too because ESC or parent shutdown closing this
|
|
// window doesn't queue a close event
|
|
save_window_state ();
|
|
}
|
|
|
|
void Dialog::closeEvent (QCloseEvent * e)
|
|
{
|
|
save_window_state ();
|
|
QDialog::closeEvent (e);
|
|
}
|
|
|
|
void Dialog::save_window_state ()
|
|
{
|
|
SettingsGroup g {settings_, title};
|
|
settings_->setValue ("geometry", saveGeometry ());
|
|
}
|
|
|
|
// 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});
|
|
}
|
|
|
|
class WSPRBandHopping::impl
|
|
{
|
|
public:
|
|
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}}
|
|
{
|
|
}
|
|
|
|
QSettings * settings_;
|
|
Configuration const * configuration_;
|
|
int tx_percent_;
|
|
QWidget * parent_widget_;
|
|
|
|
// 5 x 10 bit flags representing each hopping band in each period
|
|
// and tune
|
|
QBitArray bands_[num_periods];
|
|
|
|
int gray_line_duration_;
|
|
QPointer<Dialog> dialog_;
|
|
};
|
|
|
|
WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
|
|
: m_ {settings, configuration, parent_widget}
|
|
{
|
|
// load settings
|
|
SettingsGroup g {m_->settings_, title};
|
|
auto size = m_->settings_->beginReadArray ("periods");
|
|
for (auto i = 0; i < size; ++i)
|
|
{
|
|
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 ();
|
|
}
|
|
|
|
WSPRBandHopping::~WSPRBandHopping ()
|
|
{
|
|
// save settings
|
|
SettingsGroup g {m_->settings_, title};
|
|
m_->settings_->beginWriteArray ("periods");
|
|
for (auto i = 0u; i < num_periods; ++i)
|
|
{
|
|
m_->settings_->setArrayIndex (i);
|
|
m_->settings_->setValue ("bands", m_->bands_[i]);
|
|
}
|
|
m_->settings_->endArray ();
|
|
m_->settings_->setValue ("GrayLineDuration", m_->gray_line_duration_);
|
|
}
|
|
|
|
// pop up the maintenance dialog window
|
|
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_->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 ();
|
|
}
|
|
|
|
int WSPRBandHopping::tx_percent () const
|
|
{
|
|
return m_->tx_percent_;
|
|
}
|
|
|
|
void WSPRBandHopping::set_tx_percent (int new_value)
|
|
{
|
|
m_->tx_percent_ = new_value;
|
|
}
|
|
|
|
// determine the parameters of the hop, if any
|
|
auto WSPRBandHopping::next_hop () -> Hop
|
|
{
|
|
auto const& now = QDateTime::currentDateTimeUtc ();
|
|
auto const& date = now.date ();
|
|
auto year = date.year ();
|
|
auto month = date.month ();
|
|
auto day = date.day ();
|
|
auto const& time = now.time ();
|
|
float uth = time.hour () + time.minute () / 60.
|
|
+ (time.second () + .001 * time.msec ()) / 3600.;
|
|
auto my_grid = m_->configuration_->my_grid ();
|
|
int period_index;
|
|
int band_index;
|
|
int tx_next;
|
|
|
|
my_grid = (my_grid + " ").left (6); // hopping doesn't like
|
|
// short grids
|
|
|
|
// look up band for this period
|
|
FC_hopping (&year, &month, &day, &uth, my_grid.toLatin1 ().constData ()
|
|
, &m_->gray_line_duration_, &m_->tx_percent_, &period_index, &band_index
|
|
, &tx_next, my_grid.size ());
|
|
|
|
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]))
|
|
{
|
|
// 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 ();
|
|
}
|
|
|
|
// 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
|
|
if (frequencies_index < 0)
|
|
{
|
|
for (auto i = 0u; i < num_bands; ++i)
|
|
{
|
|
int new_index = static_cast<int> (qrand () % num_bands); // random choice
|
|
if (new_index != band_index && m_->bands_[period_index].testBit (new_index))
|
|
{
|
|
// 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)
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return {periods[period_index]
|
|
, frequencies_index
|
|
, frequencies_index >= 0 && m_->bands_[4].testBit (band_index)
|
|
, frequencies_index >= 0 && !!tx_next};
|
|
}
|