mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-09-08 22:36:40 -04:00
702 lines
24 KiB
C++
702 lines
24 KiB
C++
#include "WorkedBefore.hpp"
|
|
|
|
#include <functional>
|
|
#include <stdexcept>
|
|
#include <boost/functional/hash.hpp>
|
|
#include <boost/multi_index_container.hpp>
|
|
#include <boost/multi_index/hashed_index.hpp>
|
|
#include <boost/multi_index/ordered_index.hpp>
|
|
#include <boost/multi_index/key_extractors.hpp>
|
|
#include <boost/range/iterator_range.hpp>
|
|
#include <QCoreApplication>
|
|
#include <QtConcurrent/QtConcurrentRun>
|
|
#include <QFuture>
|
|
#include <QFutureWatcher>
|
|
#include <QChar>
|
|
#include <QString>
|
|
#include <QByteArray>
|
|
#include <QStandardPaths>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QFile>
|
|
#include <QTextStream>
|
|
#include <QDateTime>
|
|
#include "Configuration.hpp"
|
|
#include "revision_utils.hpp"
|
|
#include "qt_helpers.hpp"
|
|
#include "pimpl_impl.hpp"
|
|
|
|
#include "moc_WorkedBefore.cpp"
|
|
|
|
using namespace boost::multi_index;
|
|
|
|
// hash function for QString members in hashed indexes
|
|
inline
|
|
std::size_t hash_value (QString const& s)
|
|
{
|
|
return std::hash<QString> {} (s);
|
|
}
|
|
|
|
//
|
|
// worked before set element
|
|
//
|
|
struct worked_entry
|
|
{
|
|
explicit worked_entry (QString const& call, QString const& grid, QString const& band
|
|
, QString const& mode, QString const& country, AD1CCty::Continent continent
|
|
, int CQ_zone, int ITU_zone)
|
|
: call_ {call}
|
|
, grid_ {grid}
|
|
, band_ {band}
|
|
, mode_ {mode}
|
|
, country_ {country}
|
|
, continent_ {continent}
|
|
, CQ_zone_ {CQ_zone}
|
|
, ITU_zone_ {ITU_zone}
|
|
{
|
|
}
|
|
|
|
QString call_;
|
|
QString grid_;
|
|
QString band_;
|
|
QString mode_;
|
|
QString country_;
|
|
AD1CCty::Continent continent_;
|
|
int CQ_zone_;
|
|
int ITU_zone_;
|
|
};
|
|
|
|
bool operator == (worked_entry const& lhs, worked_entry const& rhs)
|
|
{
|
|
return
|
|
lhs.continent_ == rhs.continent_ // check 1st as it is fast
|
|
&& lhs.CQ_zone_ == rhs.CQ_zone_ // ditto
|
|
&& lhs.ITU_zone_ == rhs.ITU_zone_ // ditto
|
|
&& lhs.call_ == rhs.call_ // check the rest in decreasing
|
|
&& lhs.grid_ == rhs.grid_ // domain size order to shortcut
|
|
&& lhs.country_ == rhs.country_ // differences as quickly as possible
|
|
&& lhs.band_ == rhs.band_
|
|
&& lhs.mode_ == rhs.mode_;
|
|
}
|
|
|
|
std::size_t hash_value (worked_entry const& we)
|
|
{
|
|
std::size_t seed {0};
|
|
boost::hash_combine (seed, we.call_);
|
|
boost::hash_combine (seed, we.grid_);
|
|
boost::hash_combine (seed, we.band_);
|
|
boost::hash_combine (seed, we.mode_);
|
|
boost::hash_combine (seed, we.country_);
|
|
boost::hash_combine (seed, we.continent_);
|
|
boost::hash_combine (seed, we.CQ_zone_);
|
|
boost::hash_combine (seed, we.ITU_zone_);
|
|
return seed;
|
|
}
|
|
|
|
#if !defined (QT_NO_DEBUG_STREAM)
|
|
QDebug operator << (QDebug dbg, worked_entry const& e)
|
|
{
|
|
QDebugStateSaver saver {dbg};
|
|
dbg.nospace () << "worked_entry("
|
|
<< e.call_ << ", "
|
|
<< e.grid_ << ", "
|
|
<< e.band_ << ", "
|
|
<< e.mode_ << ", "
|
|
<< e.country_ << ", "
|
|
<< e.continent_ << ", "
|
|
<< e.CQ_zone_ << ", "
|
|
<< e.ITU_zone_ << ')';
|
|
return dbg;
|
|
}
|
|
#endif
|
|
|
|
// less then predidate for the Continent enum class, needed for
|
|
// ordered indexes
|
|
struct Continent_less
|
|
{
|
|
bool operator () (AD1CCty::Continent lhs, AD1CCty::Continent rhs) const
|
|
{
|
|
return static_cast<int> (lhs) < static_cast<int> (rhs);
|
|
}
|
|
};
|
|
|
|
// index tags
|
|
struct call_mode_band {};
|
|
struct call_band {};
|
|
struct grid_mode_band {};
|
|
struct grid_band {};
|
|
struct entity_mode_band {};
|
|
struct entity_band {};
|
|
struct continent_mode_band {};
|
|
struct continent_band {};
|
|
struct CQ_zone_mode_band {};
|
|
struct CQ_zone_band {};
|
|
struct ITU_zone_mode_band {};
|
|
struct ITU_zone_band {};
|
|
|
|
// set with multiple ordered unique indexes that allow for optimally
|
|
// efficient determination of various categories of worked before
|
|
// status
|
|
typedef multi_index_container<
|
|
worked_entry,
|
|
indexed_by<
|
|
// basic unordered set constraint - we don't need duplicate worked entries
|
|
hashed_unique<identity<worked_entry>>,
|
|
|
|
//
|
|
// The following indexes are used to discover worked before stuff.
|
|
//
|
|
// They are ordered so as to support partial lookups and
|
|
// non-unique because container inserts must be valid for all
|
|
// indexes.
|
|
//
|
|
|
|
// call+mode+band
|
|
ordered_non_unique<tag<call_mode_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, QString, &worked_entry::call_>,
|
|
member<worked_entry, QString, &worked_entry::mode_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// call+band
|
|
ordered_non_unique<tag<call_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, QString, &worked_entry::call_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// grid+mode+band
|
|
ordered_non_unique<tag<grid_mode_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, QString, &worked_entry::grid_>,
|
|
member<worked_entry, QString, &worked_entry::mode_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// grid+band
|
|
ordered_non_unique<tag<grid_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, QString, &worked_entry::grid_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// country+mode+band
|
|
ordered_non_unique<tag<entity_mode_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, QString, &worked_entry::country_>,
|
|
member<worked_entry, QString, &worked_entry::mode_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// country+band
|
|
ordered_non_unique<tag<entity_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, QString, &worked_entry::country_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// continent+mode+band
|
|
ordered_non_unique<tag<continent_mode_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, AD1CCty::Continent, &worked_entry::continent_>,
|
|
member<worked_entry, QString, &worked_entry::mode_>,
|
|
member<worked_entry, QString, &worked_entry::band_> >,
|
|
composite_key_compare<Continent_less, std::less<QString>, std::less<QString> > >,
|
|
// continent+band
|
|
ordered_non_unique<tag<continent_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, AD1CCty::Continent, &worked_entry::continent_>,
|
|
member<worked_entry, QString, &worked_entry::band_> >,
|
|
composite_key_compare<Continent_less, std::less<QString> > >,
|
|
// CQ-zone+mode+band
|
|
ordered_non_unique<tag<CQ_zone_mode_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, int, &worked_entry::CQ_zone_>,
|
|
member<worked_entry, QString, &worked_entry::mode_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// CQ-zone+band
|
|
ordered_non_unique<tag<CQ_zone_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, int, &worked_entry::CQ_zone_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// ITU-zone+mode+band
|
|
ordered_non_unique<tag<ITU_zone_mode_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, int, &worked_entry::ITU_zone_>,
|
|
member<worked_entry, QString, &worked_entry::mode_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > >,
|
|
// ITU-zone+band
|
|
ordered_non_unique<tag<ITU_zone_band>,
|
|
composite_key<worked_entry,
|
|
member<worked_entry, int, &worked_entry::ITU_zone_>,
|
|
member<worked_entry, QString, &worked_entry::band_> > > >
|
|
> worked_before_database_type;
|
|
|
|
namespace
|
|
{
|
|
auto const logFileName = "wsjtx_log.adi";
|
|
|
|
// Expception class suitable for using with QtConcurrent across
|
|
// thread boundaries
|
|
class LoaderException final
|
|
: public QException
|
|
{
|
|
public:
|
|
LoaderException (std::exception const& e) : error_ {e.what ()} {}
|
|
QString error () const {return error_;}
|
|
void raise () const override {throw *this;}
|
|
LoaderException * clone () const override {return new LoaderException {*this};}
|
|
private:
|
|
QString error_;
|
|
};
|
|
|
|
QString extractField (QString const& record, QString const& fieldName)
|
|
{
|
|
int fieldNameIndex = record.indexOf ('<' + fieldName + ':', 0, Qt::CaseInsensitive);
|
|
if (fieldNameIndex >=0)
|
|
{
|
|
int closingBracketIndex = record.indexOf('>',fieldNameIndex);
|
|
int fieldLengthIndex = record.indexOf(':',fieldNameIndex); // find the size delimiter
|
|
int dataTypeIndex = -1;
|
|
if (fieldLengthIndex >= 0)
|
|
{
|
|
dataTypeIndex = record.indexOf(':',fieldLengthIndex+1); // check for a second : indicating there is a data type
|
|
if (dataTypeIndex > closingBracketIndex)
|
|
dataTypeIndex = -1; // second : was found but it was beyond the closing >
|
|
}
|
|
else
|
|
{
|
|
throw LoaderException (std::runtime_error {QCoreApplication::translate ("WorkedBefore", "Invalid ADIF field %0: %1").arg (fieldName).arg (record).toLocal8Bit ()});
|
|
}
|
|
|
|
if (closingBracketIndex > fieldNameIndex && fieldLengthIndex > fieldNameIndex && fieldLengthIndex < closingBracketIndex)
|
|
{
|
|
int fieldLengthCharCount = closingBracketIndex - fieldLengthIndex -1;
|
|
if (dataTypeIndex >= 0)
|
|
fieldLengthCharCount -= 2; // data type indicator is always a colon followed by a single character
|
|
QString fieldLengthString = record.mid(fieldLengthIndex+1,fieldLengthCharCount);
|
|
int fieldLength = fieldLengthString.toInt();
|
|
if (fieldLength > 0)
|
|
{
|
|
return record.mid(closingBracketIndex+1,fieldLength);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw LoaderException (std::runtime_error {QCoreApplication::translate ("WorkedBefore", "Malformed ADIF field %0: %1").arg (fieldName).arg (record).toLocal8Bit ()});
|
|
}
|
|
}
|
|
return QString {};
|
|
}
|
|
|
|
worked_before_database_type loader (QString const& path, AD1CCty const * prefixes)
|
|
{
|
|
worked_before_database_type worked;
|
|
QFile inputFile {path};
|
|
if (inputFile.exists ())
|
|
{
|
|
if (inputFile.open (QFile::ReadOnly))
|
|
{
|
|
QTextStream in {&inputFile};
|
|
QString buffer;
|
|
bool pre_read {false};
|
|
int end_position {-1};
|
|
|
|
// skip optional header record
|
|
do
|
|
{
|
|
buffer += in.readLine () + '\n';
|
|
if (buffer.startsWith (QChar {'<'})) // denotes no header
|
|
{
|
|
pre_read = true;
|
|
}
|
|
else
|
|
{
|
|
end_position = buffer.indexOf ("<EOH>", 0, Qt::CaseInsensitive);
|
|
}
|
|
}
|
|
while (!in.atEnd () && !pre_read && end_position < 0);
|
|
if (!pre_read) // found header
|
|
{
|
|
if (end_position < 0)
|
|
{
|
|
throw LoaderException (std::runtime_error {QCoreApplication::translate ("WorkedBefore", "Invalid ADIF header").toLocal8Bit ()});
|
|
}
|
|
buffer.remove (0, end_position + 5);
|
|
}
|
|
while (!in.atEnd ())
|
|
{
|
|
end_position = buffer.indexOf ("<EOR>", 0, Qt::CaseInsensitive);
|
|
do
|
|
{
|
|
if (!in.atEnd () && end_position < 0)
|
|
{
|
|
buffer += in.readLine () + '\n';
|
|
}
|
|
}
|
|
while ((end_position = buffer.indexOf ("<EOR>", 0, Qt::CaseInsensitive)) < 0 && !in.atEnd ());
|
|
if (end_position >= 0) // require valid ADIF record
|
|
// with terminator
|
|
{
|
|
auto record = buffer.left (end_position + 5).trimmed ();
|
|
auto next_record = buffer.indexOf (QChar {'<'}, end_position + 5);
|
|
buffer.remove (0, next_record >=0 ? next_record : buffer.size ());
|
|
record = record.mid (record.indexOf (QChar {'<'}));
|
|
auto call = extractField (record, "CALL");
|
|
if (call.size ()) // require CALL field before we
|
|
// will parse a record
|
|
{
|
|
auto const& entity = prefixes->lookup (call);
|
|
auto mode = extractField (record, "MODE").toUpper ();
|
|
if (!mode.size () || "MFSK" == mode)
|
|
{
|
|
mode = extractField (record, "SUBMODE").toUpper ();
|
|
}
|
|
worked.emplace (call.toUpper ()
|
|
, extractField (record, "GRIDSQUARE").left (4).toUpper () // not interested in 6-digit grids
|
|
, extractField (record, "BAND").toUpper ()
|
|
, mode
|
|
, entity.entity_name
|
|
, entity.continent
|
|
, entity.CQ_zone
|
|
, entity.ITU_zone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw LoaderException (std::runtime_error {QCoreApplication::translate ("WorkedBefore", "Error opening ADIF log file for read: %0").arg (inputFile.errorString ()).toLocal8Bit ()});
|
|
}
|
|
}
|
|
return worked;
|
|
}
|
|
}
|
|
|
|
class WorkedBefore::impl final
|
|
{
|
|
public:
|
|
impl (Configuration const * configuration)
|
|
: configuration_ {configuration}
|
|
, path_ {QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath (logFileName)}
|
|
, prefixes_ {configuration}
|
|
{
|
|
}
|
|
|
|
void reload ()
|
|
{
|
|
async_loader_ = QtConcurrent::run (loader, path_, &prefixes_);
|
|
loader_watcher_.setFuture (async_loader_);
|
|
}
|
|
|
|
Configuration const * configuration_;
|
|
QString path_;
|
|
AD1CCty prefixes_;
|
|
QFutureWatcher<worked_before_database_type> loader_watcher_;
|
|
QFuture<worked_before_database_type> async_loader_;
|
|
worked_before_database_type worked_;
|
|
};
|
|
|
|
WorkedBefore::WorkedBefore (Configuration const * configuration)
|
|
: m_ {configuration}
|
|
{
|
|
Q_ASSERT (configuration);
|
|
connect (&m_->loader_watcher_, &QFutureWatcher<worked_before_database_type>::finished, [this] () {
|
|
QString error;
|
|
size_t n {0};
|
|
try
|
|
{
|
|
m_->worked_ = m_->loader_watcher_.result ();
|
|
n = m_->worked_.size ();
|
|
}
|
|
catch (LoaderException const& e)
|
|
{
|
|
error = e.error ();
|
|
}
|
|
Q_EMIT finished_loading (n, error);
|
|
});
|
|
reload ();
|
|
}
|
|
|
|
void WorkedBefore::reload ()
|
|
{
|
|
m_->reload ();
|
|
}
|
|
|
|
WorkedBefore::~WorkedBefore ()
|
|
{
|
|
}
|
|
|
|
QString const& WorkedBefore::path () const
|
|
{
|
|
return m_->path_;
|
|
}
|
|
|
|
AD1CCty const * WorkedBefore::countries () const
|
|
{
|
|
return &m_->prefixes_;
|
|
}
|
|
|
|
bool WorkedBefore::add (QString const& call
|
|
, QString const& grid
|
|
, QString const& band
|
|
, QString const& mode
|
|
, QByteArray const& ADIF_record)
|
|
{
|
|
if (call.size ())
|
|
{
|
|
auto const& entity = m_->prefixes_.lookup (call);
|
|
QFile file {m_->path_};
|
|
if (!file.open(QIODevice::Text | QIODevice::Append))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
QTextStream out {&file};
|
|
if (!file.size ())
|
|
{
|
|
auto ts = QDateTime::currentDateTimeUtc ().toString ("yyyyMMdd HHmmss");
|
|
auto ver = version (true);
|
|
out << // new file
|
|
QString {
|
|
"ADIF Export\n"
|
|
"<adif_ver:5>3.1.1\n"
|
|
"<created_timestamp:15>%0\n"
|
|
"<programid:6>WSJT-X\n"
|
|
"<programversion:%1>%2\n"
|
|
"<eoh>"
|
|
}.arg (ts).arg (ver.size ()).arg (ver)
|
|
<<
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
|
endl
|
|
#else
|
|
Qt::endl
|
|
#endif
|
|
;
|
|
}
|
|
out << ADIF_record << " <eor>" <<
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
|
endl
|
|
#else
|
|
Qt::endl
|
|
#endif
|
|
;
|
|
}
|
|
m_->worked_.emplace (call.toUpper (), grid.left (4).toUpper (), band.toUpper (), mode.toUpper ()
|
|
, entity.entity_name, entity.continent, entity.CQ_zone, entity.ITU_zone);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WorkedBefore::country_worked (QString const& country, QString const& mode, QString const& band) const
|
|
{
|
|
if (mode.size ())
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return
|
|
country.size ()
|
|
&& m_->worked_.get<entity_mode_band> ().end ()
|
|
!= m_->worked_.get<entity_mode_band> ().find (std::make_tuple (country, mode.toUpper (), band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return
|
|
country.size ()
|
|
&& m_->worked_.get<entity_mode_band> ().end ()
|
|
!= m_->worked_.get<entity_mode_band> ().find (std::make_tuple (country, mode.toUpper ()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return
|
|
country.size ()
|
|
&& m_->worked_.get<entity_band> ().end ()
|
|
!= m_->worked_.get<entity_band> ().find (std::make_tuple (country, band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return
|
|
country.size ()
|
|
&& m_->worked_.get<entity_band> ().end ()
|
|
!= m_->worked_.get<entity_band> ().find (country);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WorkedBefore::grid_worked (QString const& grid, QString const& mode, QString const& band) const
|
|
{
|
|
auto gridsquare = grid.left (4).toUpper ();
|
|
if (m_->configuration_->highlight_only_fields ())
|
|
{
|
|
// can't use a direct set find operation or a set operation with
|
|
// a (CompatibleKey, CompatibleCompare) concept so we must
|
|
// partially scan the index
|
|
auto range = boost::make_iterator_range (
|
|
m_->worked_.get<grid_mode_band> ().lower_bound (gridsquare.left (2))
|
|
, m_->worked_.get<grid_mode_band> ().upper_bound (gridsquare.left (2) + "99"));
|
|
for (worked_entry const& worked : range)
|
|
{
|
|
if ((!mode.size () || mode.toUpper () == worked.mode_)
|
|
&& (!band.size () || worked.band_ == band.toUpper ()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mode.size ())
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<grid_mode_band> ().end ()
|
|
!= m_->worked_.get<grid_mode_band> ().find (std::make_tuple (gridsquare, mode.toUpper (), band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<grid_mode_band> ().end ()
|
|
!= m_->worked_.get<grid_mode_band> ().find (std::make_tuple (gridsquare, mode.toUpper ()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<grid_band> ().end ()
|
|
!= m_->worked_.get<grid_band> ().find (std::make_tuple (gridsquare, band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<grid_band> ().end ()
|
|
!= m_->worked_.get<grid_band> ().find (gridsquare);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool WorkedBefore::call_worked (QString const& call, QString const& mode, QString const& band) const
|
|
{
|
|
if (mode.size ())
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<call_mode_band> ().end ()
|
|
!= m_->worked_.get<call_mode_band> ().find (std::make_tuple (call.toUpper (), mode.toUpper (), band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<call_mode_band> ().end ()
|
|
!= m_->worked_.get<call_mode_band> ().find (std::make_tuple (call.toUpper (), mode.toUpper ()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<call_band> ().end ()
|
|
!= m_->worked_.get<call_band> ().find (std::make_tuple (call.toUpper (), band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<call_band> ().end ()
|
|
!= m_->worked_.get<call_band> ().find (std::make_tuple (call.toUpper ()));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WorkedBefore::continent_worked (Continent continent, QString const& mode, QString const& band) const
|
|
{
|
|
if (mode.size ())
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<continent_mode_band> ().end ()
|
|
!= m_->worked_.get<continent_mode_band> ().find (std::make_tuple (continent, mode.toUpper (), band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<continent_mode_band> ().end ()
|
|
!= m_->worked_.get<continent_mode_band> ().find (std::make_tuple (continent, mode.toUpper ()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<continent_band> ().end ()
|
|
!= m_->worked_.get<continent_band> ().find (std::make_tuple (continent, band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<continent_band> ().end ()
|
|
!= m_->worked_.get<continent_band> ().find (continent);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WorkedBefore::CQ_zone_worked (int CQ_zone, QString const& mode, QString const& band) const
|
|
{
|
|
if (mode.size ())
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<CQ_zone_mode_band> ().end ()
|
|
!= m_->worked_.get<CQ_zone_mode_band> ().find (std::make_tuple (CQ_zone, mode.toUpper (), band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<CQ_zone_mode_band> ().end ()
|
|
!= m_->worked_.get<CQ_zone_mode_band> ().find (std::make_tuple (CQ_zone, mode.toUpper ()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<CQ_zone_band> ().end ()
|
|
!= m_->worked_.get<CQ_zone_band> ().find (std::make_tuple (CQ_zone, band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<CQ_zone_band> ().end ()
|
|
!= m_->worked_.get<CQ_zone_band> ().find (CQ_zone);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WorkedBefore::ITU_zone_worked (int ITU_zone, QString const& mode, QString const& band) const
|
|
{
|
|
if (mode.size ())
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<ITU_zone_mode_band> ().end ()
|
|
!= m_->worked_.get<ITU_zone_mode_band> ().find (std::make_tuple (ITU_zone, mode.toUpper (), band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<ITU_zone_mode_band> ().end ()
|
|
!= m_->worked_.get<ITU_zone_mode_band> ().find (std::make_tuple (ITU_zone, mode.toUpper ()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (band.size ())
|
|
{
|
|
return m_->worked_.get<ITU_zone_band> ().end ()
|
|
!= m_->worked_.get<ITU_zone_band> ().find (std::make_tuple (ITU_zone, band.toUpper ()));
|
|
}
|
|
else
|
|
{
|
|
// partial key lookup
|
|
return m_->worked_.get<ITU_zone_band> ().end ()
|
|
!= m_->worked_.get<ITU_zone_band> ().find (ITU_zone);
|
|
}
|
|
}
|
|
}
|