2026-01-18 12:53:55 -06:00
|
|
|
/**
|
|
|
|
|
* @file StationList.cpp
|
|
|
|
|
* @brief Implementation of StationList class
|
|
|
|
|
*/
|
2026-01-01 15:12:24 -06:00
|
|
|
|
2026-01-18 12:53:55 -06:00
|
|
|
#include "StationList.h"
|
|
|
|
|
#include "DriftingDateTime.h"
|
|
|
|
|
#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 <QDebug>
|
|
|
|
|
#include <QDebugStateSaver>
|
|
|
|
|
#include <QMetaType>
|
|
|
|
|
#include <QMimeData>
|
|
|
|
|
#include <QObject>
|
|
|
|
|
#include <QString>
|
|
|
|
|
#include <QStringList>
|
|
|
|
|
#include <QTimeZone>
|
|
|
|
|
#include <QVector>
|
|
|
|
|
|
2026-01-18 12:53:55 -06:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <utility>
|
2026-01-01 15:12:24 -06:00
|
|
|
|
|
|
|
|
#if !defined(QT_NO_DEBUG_STREAM)
|
|
|
|
|
QDebug operator<<(QDebug debug, StationList::Station const &station) {
|
|
|
|
|
QDebugStateSaver saver{debug};
|
|
|
|
|
debug.nospace() << "Station(" << station.band_name_ << ", "
|
|
|
|
|
<< station.frequency_ << ", " << station.switch_at_ << ", "
|
|
|
|
|
<< station.switch_until_ << ", " << station.description_
|
|
|
|
|
<< ')';
|
|
|
|
|
return debug;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
QDataStream &operator<<(QDataStream &os, StationList::Station const &station) {
|
|
|
|
|
return os << station.band_name_ << station.frequency_ << station.switch_at_
|
|
|
|
|
<< station.switch_until_ << station.description_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDataStream &operator>>(QDataStream &is, StationList::Station &station) {
|
|
|
|
|
is >> station.band_name_ >> station.frequency_ >> station.switch_at_ >>
|
|
|
|
|
station.switch_until_ >> station.description_;
|
|
|
|
|
|
|
|
|
|
// Set the timezones to UTC. Will be local time when read from the saved
|
|
|
|
|
// configuration
|
|
|
|
|
station.switch_at_.setTimeZone(QTimeZone::utc());
|
|
|
|
|
station.switch_until_.setTimeZone(QTimeZone::utc());
|
|
|
|
|
|
|
|
|
|
// Convert to UTC. This is necessary because the TimeSpec needs to be
|
|
|
|
|
// Qt::UTC
|
|
|
|
|
station.switch_at_ = station.switch_at_.toUTC();
|
|
|
|
|
station.switch_until_ = station.switch_until_.toUTC();
|
|
|
|
|
|
|
|
|
|
return is;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class StationList::impl final : public QAbstractTableModel {
|
|
|
|
|
public:
|
|
|
|
|
impl(Bands const *bands, Stations stations, QObject *parent)
|
|
|
|
|
: QAbstractTableModel{parent}, bands_{bands}, stations_{stations} {}
|
|
|
|
|
|
|
|
|
|
Stations station_list(Stations);
|
|
|
|
|
QModelIndex add(Station);
|
|
|
|
|
FrequencyDelta offset(Frequency) const;
|
|
|
|
|
|
|
|
|
|
// 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) const override;
|
|
|
|
|
QVariant headerData(int section, Qt::Orientation,
|
|
|
|
|
int = Qt::DisplayRole) const override;
|
|
|
|
|
bool setData(QModelIndex const &, QVariant const &value,
|
|
|
|
|
int role = Qt::EditRole) override;
|
|
|
|
|
bool removeRows(int row, int count,
|
|
|
|
|
QModelIndex const &parent = QModelIndex{}) override;
|
|
|
|
|
bool insertRows(int row, int count,
|
|
|
|
|
QModelIndex const &parent = QModelIndex{}) override;
|
|
|
|
|
Qt::DropActions supportedDropActions() const override;
|
|
|
|
|
QStringList mimeTypes() const override;
|
|
|
|
|
QMimeData *mimeData(QModelIndexList const &) const override;
|
|
|
|
|
bool dropMimeData(QMimeData const *, Qt::DropAction, int row, int column,
|
|
|
|
|
QModelIndex const &parent) override;
|
|
|
|
|
|
|
|
|
|
// Helper method for band validation.
|
|
|
|
|
QModelIndex first_matching_band(QString const &band_name) const {
|
|
|
|
|
// find first exact match in bands
|
|
|
|
|
auto matches = bands_->match(bands_->index(0, 0), Qt::DisplayRole,
|
|
|
|
|
band_name, 1, Qt::MatchExactly);
|
|
|
|
|
return matches.isEmpty() ? QModelIndex{} : matches.first();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int constexpr num_columns{5};
|
|
|
|
|
static auto constexpr mime_type = "application/wsjt.antenna-descriptions";
|
|
|
|
|
|
|
|
|
|
Bands const *bands_;
|
|
|
|
|
Stations stations_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
StationList::StationList(Bands const *bands, QObject *parent)
|
|
|
|
|
: StationList{bands, {}, parent} {}
|
|
|
|
|
|
|
|
|
|
StationList::StationList(Bands const *bands, Stations stations, QObject *parent)
|
|
|
|
|
: QSortFilterProxyModel{parent}, m_{bands, stations, parent} {
|
|
|
|
|
setSourceModel(&*m_);
|
|
|
|
|
setSortRole(SortRole);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StationList::~StationList() {}
|
|
|
|
|
|
|
|
|
|
auto StationList::station_list(Stations stations) -> Stations {
|
|
|
|
|
return m_->station_list(stations);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto StationList::station_list() const -> Stations const & {
|
|
|
|
|
return m_->stations_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QModelIndex StationList::add(Station s) { return mapFromSource(m_->add(s)); }
|
|
|
|
|
|
|
|
|
|
bool StationList::remove(Station s) {
|
|
|
|
|
auto row = m_->stations_.indexOf(s);
|
|
|
|
|
if (0 > row) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return removeRow(row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
bool row_is_higher(QModelIndex const &lhs, QModelIndex const &rhs) {
|
|
|
|
|
return lhs.row() > rhs.row();
|
|
|
|
|
}
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
bool StationList::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.
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto StationList::impl::station_list(Stations stations) -> Stations {
|
|
|
|
|
beginResetModel();
|
|
|
|
|
std::swap(stations_, stations);
|
|
|
|
|
endResetModel();
|
|
|
|
|
return stations;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QModelIndex StationList::impl::add(Station s) {
|
|
|
|
|
// Any band that isn't in the list may be added
|
|
|
|
|
if (!stations_.contains(s)) {
|
|
|
|
|
auto row = stations_.size();
|
|
|
|
|
|
|
|
|
|
beginInsertRows(QModelIndex{}, row, row);
|
|
|
|
|
stations_.append(s);
|
|
|
|
|
endInsertRows();
|
|
|
|
|
|
|
|
|
|
return index(row, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return QModelIndex{};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
auto StationList::impl::offset (Frequency f) const -> FrequencyDelta
|
|
|
|
|
{
|
|
|
|
|
// Lookup band for frequency
|
|
|
|
|
auto const& band = bands_->find (f);
|
|
|
|
|
if (!band.isEmpty ())
|
|
|
|
|
{
|
|
|
|
|
// Lookup station for band
|
|
|
|
|
for (int i = 0; i < stations_.size (); ++i)
|
|
|
|
|
{
|
|
|
|
|
if (stations_[i].band_name_ == band)
|
|
|
|
|
{
|
|
|
|
|
return stations_[i].frequency_;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0; // no offset
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
int StationList::impl::rowCount(QModelIndex const &parent) const {
|
|
|
|
|
return parent.isValid() ? 0 : stations_.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int StationList::impl::columnCount(QModelIndex const &parent) const {
|
|
|
|
|
return parent.isValid() ? 0 : num_columns;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Qt::ItemFlags StationList::impl::flags(QModelIndex const &index) const {
|
|
|
|
|
auto result = QAbstractTableModel::flags(index);
|
|
|
|
|
|
|
|
|
|
auto row = index.row();
|
|
|
|
|
auto column = index.column();
|
|
|
|
|
|
|
|
|
|
if (index.isValid() && row < stations_.size() && column < num_columns) {
|
|
|
|
|
if (band_column == column) {
|
|
|
|
|
result |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
|
|
|
|
|
|
|
|
|
|
} else if (description_column == column) {
|
|
|
|
|
result |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled |
|
|
|
|
|
Qt::ItemIsDropEnabled;
|
|
|
|
|
} else {
|
|
|
|
|
result |= Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
result |= Qt::ItemIsDropEnabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariant StationList::impl::data(QModelIndex const &index, int role) const {
|
|
|
|
|
QVariant item;
|
|
|
|
|
|
|
|
|
|
auto row = index.row();
|
|
|
|
|
auto column = index.column();
|
|
|
|
|
|
|
|
|
|
if (index.isValid() && row < stations_.size()) {
|
|
|
|
|
switch (column) {
|
|
|
|
|
case band_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole: {
|
|
|
|
|
// Lookup band.
|
|
|
|
|
auto band_index =
|
|
|
|
|
first_matching_band(stations_.at(row).band_name_);
|
|
|
|
|
// Use the sort role value of the band.
|
|
|
|
|
item = band_index.data(Bands::SortRole);
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = stations_.at(row).band_name_;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("Band name");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = Qt::AlignCenter;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case frequency_column: {
|
|
|
|
|
auto frequency_offset = stations_.at(row).frequency_;
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = frequency_offset;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
item = Radio::pretty_frequency_MHz_string(frequency_offset) +
|
|
|
|
|
" MHz";
|
|
|
|
|
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 switch_at_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = stations_.at(row).switch_at_.time().toString("hh:mm");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("Switch at this time");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = Qt::AlignCenter;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case switch_until_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = stations_.at(row).switch_until_.time().toString("hh:mm");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("Switch until this time\nFrequency will not return "
|
|
|
|
|
"at end time.\nEnter a new line to return to "
|
|
|
|
|
"original or other frequency.");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = Qt::AlignCenter;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case description_column:
|
|
|
|
|
switch (role) {
|
|
|
|
|
case SortRole:
|
|
|
|
|
case Qt::EditRole:
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
case Qt::AccessibleTextRole:
|
|
|
|
|
item = stations_.at(row).description_;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
case Qt::AccessibleDescriptionRole:
|
|
|
|
|
item = tr("Antenna description");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
item = static_cast<Qt::Alignment::Int>(Qt::AlignLeft |
|
|
|
|
|
Qt::AlignVCenter);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariant StationList::impl::headerData(int section, Qt::Orientation orientation,
|
|
|
|
|
int role) const {
|
|
|
|
|
QVariant header;
|
|
|
|
|
|
|
|
|
|
if (Qt::DisplayRole == role && Qt::Horizontal == orientation) {
|
|
|
|
|
switch (section) {
|
|
|
|
|
case band_column:
|
|
|
|
|
header = tr("Band");
|
|
|
|
|
break;
|
|
|
|
|
case frequency_column:
|
|
|
|
|
header = tr("Freq. (MHz)");
|
|
|
|
|
break;
|
|
|
|
|
case switch_at_column:
|
|
|
|
|
header = tr("Switch at (UTC)");
|
|
|
|
|
break;
|
|
|
|
|
case switch_until_column:
|
|
|
|
|
header = tr("Until (UTC)");
|
|
|
|
|
break;
|
|
|
|
|
case description_column:
|
|
|
|
|
header = tr("Description");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
header = QAbstractTableModel::headerData(section, orientation, role);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return header;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool StationList::impl::setData(QModelIndex const &model_index,
|
|
|
|
|
QVariant const &value, int role) {
|
|
|
|
|
bool changed{false};
|
|
|
|
|
|
|
|
|
|
auto row = model_index.row();
|
|
|
|
|
auto size = stations_.size();
|
|
|
|
|
if (model_index.isValid() && Qt::EditRole == role && row < size) {
|
|
|
|
|
QVector<int> roles;
|
|
|
|
|
roles << role;
|
|
|
|
|
|
|
|
|
|
switch (model_index.column()) {
|
|
|
|
|
case band_column: {
|
|
|
|
|
// Check if band name is valid.
|
|
|
|
|
auto band_index = first_matching_band(value.toString());
|
|
|
|
|
if (band_index.isValid()) {
|
|
|
|
|
stations_[row].band_name_ = band_index.data().toString();
|
|
|
|
|
Q_EMIT dataChanged(model_index, model_index, roles);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
|
|
case frequency_column: {
|
|
|
|
|
if (value.canConvert<Frequency>()) {
|
|
|
|
|
Frequency offset{qvariant_cast<Radio::Frequency>(value)};
|
|
|
|
|
|
|
|
|
|
if (offset == 0) {
|
|
|
|
|
Frequency l;
|
|
|
|
|
Frequency h;
|
|
|
|
|
if (bands_->findFreq(stations_[row].band_name_, &l, &h)) {
|
|
|
|
|
offset = l;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (offset != stations_[row].frequency_) {
|
|
|
|
|
stations_[row].frequency_ = offset;
|
|
|
|
|
Q_EMIT dataChanged(model_index, model_index, roles);
|
|
|
|
|
|
|
|
|
|
auto band = bands_->find(offset);
|
|
|
|
|
if (band != stations_[row].band_name_) {
|
|
|
|
|
stations_[row].band_name_ = band;
|
|
|
|
|
auto band_index = model_index.model()->index(
|
|
|
|
|
row, band_column, model_index);
|
|
|
|
|
Q_EMIT dataChanged(band_index, band_index, roles);
|
|
|
|
|
}
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
case switch_at_column: {
|
|
|
|
|
QString s = value.toString();
|
|
|
|
|
if (s.length() < 5) {
|
|
|
|
|
s = QString("0").repeated(5 - s.length()) + s;
|
|
|
|
|
}
|
|
|
|
|
auto t = QTime::fromString(s);
|
|
|
|
|
auto at = QDateTime(QDate(2000, 1, 1), t, QTimeZone::utc());
|
|
|
|
|
auto until = stations_[row].switch_until_;
|
|
|
|
|
stations_[row].switch_at_ = at;
|
|
|
|
|
stations_[row].switch_until_ = until;
|
|
|
|
|
|
|
|
|
|
Q_EMIT dataChanged(model_index, model_index, roles);
|
|
|
|
|
|
|
|
|
|
auto switch_until_index = model_index.model()->index(
|
|
|
|
|
row, switch_until_column, model_index);
|
|
|
|
|
Q_EMIT dataChanged(switch_until_index, switch_until_index, roles);
|
|
|
|
|
|
|
|
|
|
changed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case switch_until_column: {
|
|
|
|
|
QString s = value.toString();
|
|
|
|
|
if (s.length() < 5) {
|
|
|
|
|
s = QString("0").repeated(5 - s.length()) + s;
|
|
|
|
|
}
|
|
|
|
|
auto t = QTime::fromString(s);
|
|
|
|
|
auto until = QDateTime(QDate(2000, 1, 1), t, QTimeZone::utc());
|
|
|
|
|
auto at = stations_[row].switch_at_;
|
|
|
|
|
stations_[row].switch_at_ = at;
|
|
|
|
|
stations_[row].switch_until_ = until;
|
|
|
|
|
|
|
|
|
|
Q_EMIT dataChanged(model_index, model_index, roles);
|
|
|
|
|
|
|
|
|
|
auto switch_at_index =
|
|
|
|
|
model_index.model()->index(row, switch_at_column, model_index);
|
|
|
|
|
Q_EMIT dataChanged(switch_at_index, switch_at_index, roles);
|
|
|
|
|
|
|
|
|
|
changed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case description_column:
|
|
|
|
|
stations_[row].description_ = value.toString();
|
|
|
|
|
Q_EMIT dataChanged(model_index, model_index, roles);
|
|
|
|
|
changed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool StationList::impl::removeRows(int row, int count,
|
|
|
|
|
QModelIndex const &parent) {
|
|
|
|
|
if (0 < count && (row + count) <= rowCount(parent)) {
|
|
|
|
|
beginRemoveRows(parent, row, row + count - 1);
|
|
|
|
|
for (auto r = 0; r < count; ++r) {
|
|
|
|
|
stations_.removeAt(row);
|
|
|
|
|
}
|
|
|
|
|
endRemoveRows();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool StationList::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) {
|
|
|
|
|
stations_.insert(row, Station());
|
|
|
|
|
}
|
|
|
|
|
endInsertRows();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Qt::DropActions StationList::impl::supportedDropActions() const {
|
|
|
|
|
return Qt::CopyAction | Qt::MoveAction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList StationList::impl::mimeTypes() const {
|
|
|
|
|
QStringList types;
|
|
|
|
|
types << mime_type;
|
|
|
|
|
types << "application/wsjt.Frequencies";
|
|
|
|
|
return types;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QMimeData *StationList::impl::mimeData(QModelIndexList const &items) const {
|
|
|
|
|
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()) {
|
|
|
|
|
stream << QString{data(item, Qt::DisplayRole).toString()};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mime_data->setData(mime_type, encoded_data);
|
|
|
|
|
return mime_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool StationList::impl::dropMimeData(QMimeData const *data,
|
|
|
|
|
Qt::DropAction action, int /* row */,
|
|
|
|
|
int /* column */,
|
|
|
|
|
QModelIndex const &parent) {
|
|
|
|
|
if (Qt::IgnoreAction == action) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (parent.isValid() && description_column == parent.column() &&
|
|
|
|
|
data->hasFormat(mime_type)) {
|
|
|
|
|
QByteArray encoded_data{data->data(mime_type)};
|
|
|
|
|
QDataStream stream{&encoded_data, QIODevice::ReadOnly};
|
|
|
|
|
auto dest_index = parent;
|
|
|
|
|
while (!stream.atEnd()) {
|
|
|
|
|
QString text;
|
|
|
|
|
stream >> text;
|
|
|
|
|
setData(dest_index, text);
|
|
|
|
|
dest_index =
|
|
|
|
|
index(dest_index.row() + 1, dest_index.column(), QModelIndex{});
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
} else if (data->hasFormat("application/wsjt.Frequencies")) {
|
|
|
|
|
QByteArray encoded_data{data->data("application/wsjt.Frequencies")};
|
|
|
|
|
QDataStream stream{&encoded_data, QIODevice::ReadOnly};
|
|
|
|
|
while (!stream.atEnd()) {
|
2026-02-07 21:16:28 -05:00
|
|
|
FrequencyList_v3::Item item;
|
2026-01-01 15:12:24 -06:00
|
|
|
stream >> item;
|
|
|
|
|
auto const &band = bands_->find(item.frequency_);
|
|
|
|
|
if (stations_.cend() ==
|
|
|
|
|
std::find_if(stations_.cbegin(), stations_.cend(),
|
|
|
|
|
[&band](Station const &s) {
|
|
|
|
|
return s.band_name_ == band;
|
|
|
|
|
})) {
|
|
|
|
|
// not found so add it
|
|
|
|
|
add(Station{band, 0, DriftingDateTime::currentDateTimeUtc(),
|
|
|
|
|
DriftingDateTime::currentDateTimeUtc(), QString{}});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|