2026-01-18 12:53:55 -06:00
|
|
|
/**
|
|
|
|
|
* @file FrequencyList.cpp
|
|
|
|
|
* @brief Implementation of the JS8 standard operating frequencies
|
|
|
|
|
*/
|
2026-01-01 15:12:24 -06:00
|
|
|
|
2026-01-18 12:53:55 -06:00
|
|
|
#include "FrequencyList.h"
|
|
|
|
|
#include "JS8_Include/pimpl_impl.h"
|
|
|
|
|
#include "JS8_Main/Bands.h"
|
|
|
|
|
#include "Radio.h"
|
2026-01-01 15:12:24 -06:00
|
|
|
|
|
|
|
|
#include <QAbstractTableModel>
|
|
|
|
|
#include <QByteArray>
|
|
|
|
|
#include <QDataStream>
|
|
|
|
|
#include <QDebugStateSaver>
|
|
|
|
|
#include <QList>
|
|
|
|
|
#include <QListIterator>
|
|
|
|
|
#include <QMetaType>
|
|
|
|
|
#include <QMimeData>
|
|
|
|
|
#include <QString>
|
|
|
|
|
#include <QStringList>
|
|
|
|
|
#include <QVector>
|
|
|
|
|
|
2026-01-18 12:53:55 -06:00
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <limits>
|
|
|
|
|
#include <utility>
|
2026-01-01 15:12:24 -06:00
|
|
|
|
|
|
|
|
#include "moc_FrequencyList.cpp"
|
|
|
|
|
|
|
|
|
|
namespace {
|
2026-02-07 21:16:28 -05:00
|
|
|
FrequencyList_v3::FrequencyItems const default_frequency_list = {
|
|
|
|
|
{1843500, Modes::JS8, IARURegions::ALL, ""}, // 3.5 above
|
|
|
|
|
{3578000, Modes::JS8, IARURegions::ALL, ""}, // 5 above
|
2026-01-01 15:12:24 -06:00
|
|
|
{5363000, Modes::JS8,
|
2026-02-07 21:16:28 -05:00
|
|
|
IARURegions::ALL, ""}, // not known at this time where FT8 will be
|
|
|
|
|
{7078000, Modes::JS8, IARURegions::ALL, ""}, // 4 above
|
|
|
|
|
{10130000, Modes::JS8, IARURegions::ALL, ""}, // 6 below
|
|
|
|
|
{14078000, Modes::JS8, IARURegions::ALL, ""}, // 4 above
|
|
|
|
|
{18104000, Modes::JS8, IARURegions::ALL, ""}, // 4 above
|
|
|
|
|
{21078000, Modes::JS8, IARURegions::ALL, ""}, // 4 above
|
|
|
|
|
{24922000, Modes::JS8, IARURegions::ALL, ""}, // 9 above
|
|
|
|
|
{28078000, Modes::JS8, IARURegions::ALL, ""}, // 4 above
|
|
|
|
|
{50318000, Modes::JS8, IARURegions::ALL, ""}, // 5 above
|
|
|
|
|
{144178000, Modes::JS8, IARURegions::ALL, ""}, // 4 above
|
2026-01-01 15:12:24 -06:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if !defined(QT_NO_DEBUG_STREAM)
|
2026-02-07 21:16:28 -05:00
|
|
|
QDebug operator<<(QDebug debug, FrequencyList_v3::Item const &item) {
|
2026-01-01 15:12:24 -06:00
|
|
|
QDebugStateSaver saver{debug};
|
|
|
|
|
debug.nospace() << "FrequencyItem(" << item.frequency_ << ", "
|
|
|
|
|
<< item.region_ << ", " << item.mode_ << ')';
|
|
|
|
|
return debug;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
QDataStream &operator<<(QDataStream &os, FrequencyList_v3::Item const &item) {
|
|
|
|
|
return os << item.frequency_ << item.mode_ << item.region_ << item.description_;
|
2026-01-01 15:12:24 -06:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
QDataStream &operator>>(QDataStream &is, FrequencyList_v3::Item &item) {
|
|
|
|
|
return is >> item.frequency_ >> item.mode_ >> item.region_ >> item.description_;
|
2026-01-01 15:12:24 -06:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
class FrequencyList_v3::impl final : public QAbstractTableModel {
|
2026-01-01 15:12:24 -06:00
|
|
|
public:
|
|
|
|
|
impl(Bands const *bands, QObject *parent)
|
|
|
|
|
: QAbstractTableModel{parent}, bands_{bands},
|
|
|
|
|
region_filter_{IARURegions::ALL}, mode_filter_{Modes::ALL} {}
|
|
|
|
|
|
|
|
|
|
FrequencyItems frequency_list(FrequencyItems);
|
|
|
|
|
QModelIndex add(Item);
|
|
|
|
|
void add(FrequencyItems);
|
|
|
|
|
|
|
|
|
|
// Implement the QAbstractTableModel interface
|
|
|
|
|
int rowCount(QModelIndex const &parent = QModelIndex{}) const override;
|
|
|
|
|
int columnCount(QModelIndex const &parent = QModelIndex{}) const override;
|
|
|
|
|
Qt::ItemFlags flags(QModelIndex const & = QModelIndex{}) const override;
|
|
|
|
|
QVariant data(QModelIndex const &,
|
|
|
|
|
int role = Qt::DisplayRole) const override;
|
|
|
|
|
bool setData(QModelIndex const &, QVariant const &value,
|
|
|
|
|
int role = Qt::EditRole) override;
|
|
|
|
|
QVariant headerData(int section, Qt::Orientation,
|
|
|
|
|
int = Qt::DisplayRole) const override;
|
|
|
|
|
bool removeRows(int row, int count,
|
|
|
|
|
QModelIndex const &parent = QModelIndex{}) override;
|
|
|
|
|
bool insertRows(int row, int count,
|
|
|
|
|
QModelIndex const &parent = QModelIndex{}) override;
|
|
|
|
|
QStringList mimeTypes() const override;
|
|
|
|
|
QMimeData *mimeData(QModelIndexList const &) const override;
|
|
|
|
|
|
|
|
|
|
static int constexpr num_cols{SENTINAL};
|
|
|
|
|
static auto constexpr mime_type = "application/wsjt.Frequencies";
|
|
|
|
|
|
|
|
|
|
Bands const *bands_;
|
|
|
|
|
FrequencyItems frequency_list_;
|
|
|
|
|
Region region_filter_;
|
|
|
|
|
Mode mode_filter_;
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
FrequencyList_v3::FrequencyList_v3(Bands const *bands, QObject *parent)
|
2026-01-01 15:12:24 -06:00
|
|
|
: QSortFilterProxyModel{parent}, m_{bands, parent} {
|
|
|
|
|
setSourceModel(&*m_);
|
|
|
|
|
setSortRole(SortRole);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
FrequencyList_v3::~FrequencyList_v3() {}
|
2026-01-01 15:12:24 -06:00
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::frequency_list(FrequencyItems frequency_list)
|
2026-01-01 15:12:24 -06:00
|
|
|
-> FrequencyItems {
|
|
|
|
|
return m_->frequency_list(frequency_list);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::frequency_list() const -> FrequencyItems const & {
|
2026-01-01 15:12:24 -06:00
|
|
|
return m_->frequency_list_;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::frequency_list(
|
2026-01-01 15:12:24 -06:00
|
|
|
QModelIndexList const &model_index_list) const -> FrequencyItems {
|
|
|
|
|
FrequencyItems list;
|
2026-06-17 19:29:34 -05:00
|
|
|
for (auto const &index : std::as_const(model_index_list)) {
|
2026-01-01 15:12:24 -06:00
|
|
|
list << m_->frequency_list_[mapToSource(index).row()];
|
|
|
|
|
}
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
void FrequencyList_v3::frequency_list_merge(FrequencyItems const &items) {
|
2026-01-01 15:12:24 -06:00
|
|
|
m_->add(items);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
int FrequencyList_v3::best_working_frequency(Frequency f) const {
|
2026-01-01 15:12:24 -06:00
|
|
|
int result{-1};
|
|
|
|
|
auto const &target_band = m_->bands_->find(f);
|
|
|
|
|
if (!target_band.isEmpty()) {
|
|
|
|
|
Radio::FrequencyDelta delta{
|
|
|
|
|
std::numeric_limits<Radio::FrequencyDelta>::max()};
|
|
|
|
|
// find a frequency in the same band that is allowed
|
|
|
|
|
for (int row = 0; row < rowCount(); ++row) {
|
|
|
|
|
auto const &source_row = mapToSource(index(row, 0)).row();
|
|
|
|
|
auto const &candidate_frequency =
|
|
|
|
|
m_->frequency_list_[source_row].frequency_;
|
|
|
|
|
auto const &band = m_->bands_->find(candidate_frequency);
|
|
|
|
|
if (band == target_band) {
|
|
|
|
|
// take closest band match
|
|
|
|
|
Radio::FrequencyDelta new_delta = f - candidate_frequency;
|
|
|
|
|
if (std::abs(new_delta) < std::abs(delta)) {
|
|
|
|
|
delta = new_delta;
|
|
|
|
|
result = row;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
int FrequencyList_v3::best_working_frequency(QString const &target_band) const {
|
2026-01-01 15:12:24 -06:00
|
|
|
int result{-1};
|
|
|
|
|
if (!target_band.isEmpty()) {
|
|
|
|
|
// find a frequency in the same band that is allowed
|
|
|
|
|
for (int row = 0; row < rowCount(); ++row) {
|
|
|
|
|
auto const &source_row = mapToSource(index(row, 0)).row();
|
|
|
|
|
auto const &band =
|
|
|
|
|
m_->bands_->find(m_->frequency_list_[source_row].frequency_);
|
|
|
|
|
if (band == target_band) {
|
|
|
|
|
return row;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
void FrequencyList_v3::reset_to_defaults() {
|
2026-01-01 15:12:24 -06:00
|
|
|
m_->frequency_list(default_frequency_list);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
QModelIndex FrequencyList_v3::add(Item f) { return mapFromSource(m_->add(f)); }
|
2026-01-01 15:12:24 -06:00
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
bool FrequencyList_v3::remove(Item f) {
|
2026-01-01 15:12:24 -06:00
|
|
|
auto row = m_->frequency_list_.indexOf(f);
|
|
|
|
|
|
|
|
|
|
if (0 > row) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return m_->removeRow(row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
bool row_is_higher(QModelIndex const &lhs, QModelIndex const &rhs) {
|
|
|
|
|
return lhs.row() > rhs.row();
|
|
|
|
|
}
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
bool FrequencyList_v3::removeDisjointRows(QModelIndexList rows) {
|
2026-01-01 15:12:24 -06:00
|
|
|
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.
|
|
|
|
|
for (int r = 0; r < rows.size(); ++r) {
|
|
|
|
|
rows[r] = mapToSource(rows[r]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reverse sort by row
|
|
|
|
|
std::sort(rows.begin(), rows.end(), row_is_higher);
|
2026-06-17 19:29:34 -05:00
|
|
|
for (auto index : std::as_const(rows)) {
|
2026-01-01 15:12:24 -06:00
|
|
|
if (result && !m_->removeRow(index.row())) {
|
|
|
|
|
result = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
void FrequencyList_v3::filter(Region region, Mode mode) {
|
|
|
|
|
if (m_->region_filter_ == region && m_->mode_filter_ == mode)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-06-15 06:20:57 -05:00
|
|
|
beginFilterChange();
|
2026-01-01 15:12:24 -06:00
|
|
|
m_->region_filter_ = region;
|
2026-02-07 21:16:28 -05:00
|
|
|
m_->mode_filter_ = mode;
|
2026-06-15 06:20:57 -05:00
|
|
|
endFilterChange();
|
2026-01-01 15:12:24 -06:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
bool FrequencyList_v3::filterAcceptsRow(
|
2026-01-01 15:12:24 -06:00
|
|
|
int source_row, QModelIndex const & /* parent */) const {
|
|
|
|
|
bool result{true};
|
|
|
|
|
auto const &item = m_->frequency_list_[source_row];
|
|
|
|
|
if (m_->region_filter_ != IARURegions::ALL) {
|
|
|
|
|
result = IARURegions::ALL == item.region_ ||
|
|
|
|
|
m_->region_filter_ == item.region_;
|
|
|
|
|
}
|
|
|
|
|
if (result && m_->mode_filter_ != Modes::ALL) {
|
|
|
|
|
result = Modes::ALL == item.mode_ || m_->mode_filter_ == item.mode_;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::impl::frequency_list(FrequencyItems frequency_list)
|
2026-01-01 15:12:24 -06:00
|
|
|
-> FrequencyItems {
|
|
|
|
|
beginResetModel();
|
|
|
|
|
std::swap(frequency_list_, frequency_list);
|
|
|
|
|
endResetModel();
|
|
|
|
|
return frequency_list;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
QModelIndex FrequencyList_v3::impl::add(Item f) {
|
2026-01-01 15:12:24 -06:00
|
|
|
// Any Frequency that isn't in the list may be added
|
|
|
|
|
if (!frequency_list_.contains(f)) {
|
|
|
|
|
auto row = frequency_list_.size();
|
|
|
|
|
|
|
|
|
|
beginInsertRows(QModelIndex{}, row, row);
|
|
|
|
|
frequency_list_.append(f);
|
|
|
|
|
endInsertRows();
|
|
|
|
|
|
|
|
|
|
return index(row, 0);
|
|
|
|
|
}
|
|
|
|
|
return QModelIndex{};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
void FrequencyList_v3::impl::add(FrequencyItems items) {
|
2026-01-01 15:12:24 -06:00
|
|
|
// Any Frequency that isn't in the list may be added
|
|
|
|
|
for (auto p = items.begin(); p != items.end();) {
|
|
|
|
|
if (frequency_list_.contains(*p)) {
|
|
|
|
|
p = items.erase(p);
|
|
|
|
|
} else {
|
|
|
|
|
++p;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (items.size()) {
|
|
|
|
|
auto row = frequency_list_.size();
|
|
|
|
|
|
|
|
|
|
beginInsertRows(QModelIndex{}, row, row + items.size() - 1);
|
|
|
|
|
frequency_list_.append(items);
|
|
|
|
|
endInsertRows();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
int FrequencyList_v3::impl::rowCount(QModelIndex const &parent) const {
|
2026-01-01 15:12:24 -06:00
|
|
|
return parent.isValid() ? 0 : frequency_list_.size();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
int FrequencyList_v3::impl::columnCount(QModelIndex const &parent) const {
|
2026-01-01 15:12:24 -06:00
|
|
|
return parent.isValid() ? 0 : num_cols;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
Qt::ItemFlags FrequencyList_v3::impl::flags(QModelIndex const &index) const {
|
2026-01-01 15:12:24 -06:00
|
|
|
auto result = QAbstractTableModel::flags(index) | Qt::ItemIsDropEnabled;
|
|
|
|
|
auto row = index.row();
|
|
|
|
|
auto column = index.column();
|
|
|
|
|
if (index.isValid() && row < frequency_list_.size() && column < num_cols) {
|
|
|
|
|
if (frequency_mhz_column != column) {
|
|
|
|
|
result |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
QVariant FrequencyList_v3::impl::data(QModelIndex const &index,
|
2026-01-01 15:12:24 -06:00
|
|
|
int role) const {
|
|
|
|
|
QVariant item;
|
|
|
|
|
|
|
|
|
|
auto const &row = index.row();
|
|
|
|
|
auto const &column = index.column();
|
|
|
|
|
|
|
|
|
|
if (index.isValid() && row < frequency_list_.size() && column < num_cols) {
|
|
|
|
|
auto const &frequency_item = frequency_list_.at(row);
|
|
|
|
|
switch (column) {
|
|
|
|
|
case region_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole:
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = IARURegions::name(frequency_item.region_);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("IARU Region");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = Qt::AlignCenter;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case mode_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole:
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = Modes::name(frequency_item.mode_);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("Mode");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = Qt::AlignCenter;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case frequency_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = frequency_item.frequency_;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
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) + ')';
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("Frequency");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = static_cast<Qt::Alignment::Int>(Qt::AlignRight |
|
|
|
|
|
Qt::AlignVCenter);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case frequency_mhz_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = Radio::frequency_MHz_string(frequency_item.frequency_);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
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) + ')';
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("Frequency (MHz)");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = static_cast<Qt::Alignment::Int>(Qt::AlignRight |
|
|
|
|
|
Qt::AlignVCenter);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-02-07 21:16:28 -05:00
|
|
|
|
|
|
|
|
case description_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = frequency_item.description_;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::DisplayRole: {
|
|
|
|
|
item = frequency_item.description_;
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("Description");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = static_cast<Qt::Alignment::Int>(Qt::AlignLeft |
|
|
|
|
|
Qt::AlignVCenter);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-01-01 15:12:24 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
bool FrequencyList_v3::impl::setData(QModelIndex const &model_index,
|
2026-01-01 15:12:24 -06:00
|
|
|
QVariant const &value, int role) {
|
|
|
|
|
bool changed{false};
|
|
|
|
|
|
|
|
|
|
auto const &row = model_index.row();
|
|
|
|
|
if (model_index.isValid() && Qt::EditRole == role &&
|
|
|
|
|
row < frequency_list_.size()) {
|
|
|
|
|
QVector<int> roles;
|
|
|
|
|
roles << role;
|
|
|
|
|
|
|
|
|
|
auto &item = frequency_list_[row];
|
|
|
|
|
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<Frequency>()) {
|
|
|
|
|
Radio::Frequency frequency{
|
|
|
|
|
qvariant_cast<Radio::Frequency>(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;
|
2026-02-07 21:16:28 -05:00
|
|
|
|
|
|
|
|
case description_column: {
|
|
|
|
|
auto description = value.toString();
|
|
|
|
|
if (description != item.description_) {
|
|
|
|
|
item.description_ = description;
|
|
|
|
|
Q_EMIT dataChanged(model_index, model_index, roles);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
} break;
|
2026-01-01 15:12:24 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
QVariant FrequencyList_v3::impl::headerData(int section,
|
2026-01-01 15:12:24 -06:00
|
|
|
Qt::Orientation orientation,
|
|
|
|
|
int role) const {
|
|
|
|
|
QVariant header;
|
|
|
|
|
if (Qt::DisplayRole == role && Qt::Horizontal == orientation &&
|
|
|
|
|
section < num_cols) {
|
|
|
|
|
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;
|
2026-02-07 21:16:28 -05:00
|
|
|
case description_column:
|
|
|
|
|
header = tr("Description");
|
|
|
|
|
break;
|
2026-01-01 15:12:24 -06:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
header = QAbstractTableModel::headerData(section, orientation, role);
|
|
|
|
|
}
|
|
|
|
|
return header;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
bool FrequencyList_v3::impl::removeRows(int row, int count,
|
2026-01-01 15:12:24 -06:00
|
|
|
QModelIndex const &parent) {
|
|
|
|
|
if (0 < count && (row + count) <= rowCount(parent)) {
|
|
|
|
|
beginRemoveRows(parent, row, row + count - 1);
|
|
|
|
|
for (auto r = 0; r < count; ++r) {
|
|
|
|
|
frequency_list_.removeAt(row);
|
|
|
|
|
}
|
|
|
|
|
endRemoveRows();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
bool FrequencyList_v3::impl::insertRows(int row, int count,
|
2026-01-01 15:12:24 -06:00
|
|
|
QModelIndex const &parent) {
|
|
|
|
|
if (0 < count) {
|
|
|
|
|
beginInsertRows(parent, row, row + count - 1);
|
|
|
|
|
for (auto r = 0; r < count; ++r) {
|
2026-02-07 21:16:28 -05:00
|
|
|
frequency_list_.insert(row, Item{0, Mode::ALL, IARURegions::ALL, ""});
|
2026-01-01 15:12:24 -06:00
|
|
|
}
|
|
|
|
|
endInsertRows();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
QStringList FrequencyList_v3::impl::mimeTypes() const {
|
2026-01-01 15:12:24 -06:00
|
|
|
QStringList types;
|
|
|
|
|
types << mime_type;
|
|
|
|
|
return types;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QMimeData *
|
2026-02-07 21:16:28 -05:00
|
|
|
FrequencyList_v3::impl::mimeData(QModelIndexList const &items) const {
|
2026-01-01 15:12:24 -06:00
|
|
|
QMimeData *mime_data = new QMimeData{};
|
|
|
|
|
QByteArray encoded_data;
|
|
|
|
|
QDataStream stream{&encoded_data, QIODevice::WriteOnly};
|
|
|
|
|
|
2026-06-17 19:29:34 -05:00
|
|
|
for (auto const &item : std::as_const(items)) {
|
2026-01-01 15:12:24 -06:00
|
|
|
if (item.isValid() && frequency_column == item.column()) {
|
|
|
|
|
stream << frequency_list_.at(item.row());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mime_data->setData(mime_type, encoded_data);
|
|
|
|
|
return mime_data;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::const_iterator::operator*() const -> Item const & {
|
2026-01-01 15:12:24 -06:00
|
|
|
return parent_->frequency_list().at(
|
|
|
|
|
parent_->mapToSource(parent_->index(row_, 0)).row());
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::const_iterator::operator->() const -> Item const * {
|
2026-01-01 15:12:24 -06:00
|
|
|
return &parent_->frequency_list().at(
|
|
|
|
|
parent_->mapToSource(parent_->index(row_, 0)).row());
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
bool FrequencyList_v3::const_iterator::operator!=(
|
2026-01-01 15:12:24 -06:00
|
|
|
const_iterator const &rhs) const {
|
|
|
|
|
return parent_ != rhs.parent_ || row_ != rhs.row_;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
bool FrequencyList_v3::const_iterator::operator==(
|
2026-01-01 15:12:24 -06:00
|
|
|
const_iterator const &rhs) const {
|
|
|
|
|
return parent_ == rhs.parent_ && row_ == rhs.row_;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::const_iterator::operator++() -> const_iterator & {
|
2026-01-01 15:12:24 -06:00
|
|
|
++row_;
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::begin() const -> const_iterator {
|
2026-01-01 15:12:24 -06:00
|
|
|
return const_iterator(this, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::end() const -> const_iterator {
|
2026-01-01 15:12:24 -06:00
|
|
|
return const_iterator(this, rowCount());
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::find(Frequency f) const -> const_iterator {
|
2026-01-01 15:12:24 -06:00
|
|
|
int row{0};
|
|
|
|
|
for (; row < rowCount(); ++row) {
|
|
|
|
|
if (m_->frequency_list_[mapToSource(index(row, 0)).row()].frequency_ ==
|
|
|
|
|
f) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return const_iterator(this, row);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::filtered_bands() const -> BandSet {
|
2026-01-01 15:12:24 -06:00
|
|
|
BandSet result;
|
|
|
|
|
for (auto const &item : *this) {
|
|
|
|
|
result << m_->bands_->find(item.frequency_);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 21:16:28 -05:00
|
|
|
auto FrequencyList_v3::all_bands(Region region, Mode mode) const -> BandSet {
|
2026-01-01 15:12:24 -06:00
|
|
|
BandSet result;
|
|
|
|
|
for (auto const &item : m_->frequency_list_) {
|
|
|
|
|
if (region == IARURegions::ALL || item.region_ == region ||
|
|
|
|
|
mode == Modes::ALL || item.mode_ == mode) {
|
|
|
|
|
result << m_->bands_->find(item.frequency_);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Obsolete version of FrequencyList no longer used but needed to
|
|
|
|
|
// allow loading and saving of old settings contents without damage
|
|
|
|
|
//
|
|
|
|
|
QDataStream &operator<<(QDataStream &os, FrequencyList::Item const &item) {
|
|
|
|
|
return os << item.frequency_ << item.mode_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDataStream &operator>>(QDataStream &is, FrequencyList::Item &item) {
|
|
|
|
|
return is >> item.frequency_ >> item.mode_;
|
|
|
|
|
}
|
2026-02-07 21:16:28 -05:00
|
|
|
|
|
|
|
|
QDataStream &operator<<(QDataStream &os, FrequencyList_v2::Item const &item) {
|
|
|
|
|
return os << item.frequency_ << item.mode_ << item.region_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDataStream &operator>>(QDataStream &is, FrequencyList_v2::Item &item) {
|
|
|
|
|
return is >> item.frequency_ >> item.mode_ >> item.region_;
|
2026-06-15 06:20:57 -05:00
|
|
|
}
|