2018-11-11 21:11:58 -05:00
|
|
|
#include "CabrilloLog.hpp"
|
|
|
|
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <utility>
|
|
|
|
#include <QString>
|
|
|
|
#include <QDateTime>
|
|
|
|
#include <QSqlDatabase>
|
|
|
|
#include <QSqlTableModel>
|
|
|
|
#include <QSqlRecord>
|
|
|
|
#include <QSqlError>
|
|
|
|
#include <QSqlQuery>
|
2018-11-12 15:10:27 -05:00
|
|
|
#include <QDataStream>
|
2018-11-11 21:11:58 -05:00
|
|
|
#include "Configuration.hpp"
|
|
|
|
#include "Bands.hpp"
|
2019-05-29 18:35:18 -04:00
|
|
|
#include "logbook/AD1CCty.hpp"
|
2018-11-11 21:11:58 -05:00
|
|
|
#include "qt_db_helpers.hpp"
|
|
|
|
#include "pimpl_impl.hpp"
|
|
|
|
|
|
|
|
class CabrilloLog::impl final
|
|
|
|
: public QSqlTableModel
|
|
|
|
{
|
|
|
|
public:
|
2019-05-30 17:20:09 -04:00
|
|
|
impl (CabrilloLog *, Configuration const *);
|
2018-11-11 21:11:58 -05:00
|
|
|
|
2019-05-30 17:20:09 -04:00
|
|
|
int columnCount (QModelIndex const& /*index */) const override
|
2019-05-03 05:21:50 -04:00
|
|
|
{
|
2019-05-30 17:20:09 -04:00
|
|
|
return QSqlTableModel::columnCount () + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags flags (QModelIndex const& index) const override
|
|
|
|
{
|
|
|
|
auto flags = QSqlTableModel::flags (index);
|
|
|
|
if (index.isValid () && index.column () == columnCount (index) - 1)
|
2019-05-03 05:21:50 -04:00
|
|
|
{
|
2019-05-30 17:20:09 -04:00
|
|
|
flags = Qt::ItemIsEnabled;
|
|
|
|
}
|
|
|
|
return flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant data (QModelIndex const& model_index, int role) const override
|
|
|
|
{
|
|
|
|
QVariant value;
|
|
|
|
if (model_index.isValid () && model_index.column () == columnCount (model_index) - 1)
|
|
|
|
{ // derive band column
|
2019-05-03 05:21:50 -04:00
|
|
|
if (Qt::DisplayRole == role)
|
|
|
|
{
|
2019-05-30 17:20:09 -04:00
|
|
|
value = configuration_->bands ()->find (QSqlTableModel::data (index (model_index.row (), fieldIndex ("frequency"))).toULongLong ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
value = QSqlTableModel::data (model_index, role);
|
2019-12-08 13:37:35 -05:00
|
|
|
if (Qt::DisplayRole == role)
|
2019-05-30 17:20:09 -04:00
|
|
|
{
|
2019-12-08 13:37:35 -05:00
|
|
|
if (model_index.column () == fieldIndex ("frequency"))
|
2019-05-30 17:20:09 -04:00
|
|
|
{
|
2019-12-08 13:37:35 -05:00
|
|
|
value = Radio::frequency_MHz_string (value.value<Radio::Frequency> (), 3); // kHz precision
|
|
|
|
}
|
|
|
|
else if (model_index.column () == fieldIndex ("when"))
|
|
|
|
{ // adjust date/time to Qt format
|
2019-05-30 17:20:09 -04:00
|
|
|
QLocale locale;
|
2019-12-08 13:37:35 -05:00
|
|
|
value = locale.toString (QDateTime::fromMSecsSinceEpoch (value.toULongLong () * 1000ull, Qt::UTC), locale.dateFormat (QLocale::ShortFormat) + " hh:mm:ss");
|
2019-05-30 17:20:09 -04:00
|
|
|
}
|
2019-05-03 05:21:50 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2019-01-21 17:03:33 -05:00
|
|
|
QString cabrillo_frequency_string (Radio::Frequency frequency) const;
|
2019-05-30 17:20:09 -04:00
|
|
|
void create_table ();
|
2019-01-21 17:03:33 -05:00
|
|
|
|
2019-05-30 17:20:09 -04:00
|
|
|
CabrilloLog * self_;
|
2018-11-11 21:11:58 -05:00
|
|
|
Configuration const * configuration_;
|
|
|
|
QSqlQuery mutable dupe_query_;
|
|
|
|
QSqlQuery mutable export_query_;
|
2019-05-30 17:20:09 -04:00
|
|
|
bool adding_row_;
|
2018-11-11 21:11:58 -05:00
|
|
|
};
|
|
|
|
|
2019-05-30 17:20:09 -04:00
|
|
|
CabrilloLog::impl::impl (CabrilloLog * self, Configuration const * configuration)
|
|
|
|
: self_ {self}
|
2018-11-11 21:11:58 -05:00
|
|
|
, configuration_ {configuration}
|
2019-05-30 17:20:09 -04:00
|
|
|
, adding_row_ {false}
|
2018-11-11 21:11:58 -05:00
|
|
|
{
|
2019-05-30 18:29:46 -04:00
|
|
|
if (!database ().tables ().contains ("cabrillo_log_v2"))
|
2019-05-30 17:20:09 -04:00
|
|
|
{
|
|
|
|
create_table ();
|
|
|
|
}
|
|
|
|
|
|
|
|
setEditStrategy (QSqlTableModel::OnFieldChange);
|
2019-05-30 18:29:46 -04:00
|
|
|
setTable ("cabrillo_log_v2");
|
2019-05-30 17:20:09 -04:00
|
|
|
setHeaderData (fieldIndex ("frequency"), Qt::Horizontal, tr ("Freq(MHz)"));
|
|
|
|
setHeaderData (fieldIndex ("mode"), Qt::Horizontal, tr ("Mode"));
|
|
|
|
setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)"));
|
|
|
|
setHeaderData (fieldIndex ("call"), Qt::Horizontal, tr ("Call"));
|
|
|
|
setHeaderData (fieldIndex ("exchange_sent"), Qt::Horizontal, tr ("Sent"));
|
|
|
|
setHeaderData (fieldIndex ("exchange_rcvd"), Qt::Horizontal, tr ("Rcvd"));
|
|
|
|
setHeaderData (columnCount (QModelIndex {}) - 1, Qt::Horizontal, tr ("Band"));
|
|
|
|
|
|
|
|
// This descending order by time is important, it makes the view
|
|
|
|
// place the latest row at the top, without this the model/view
|
|
|
|
// interactions are both sluggish and unhelpful.
|
|
|
|
setSort (fieldIndex ("when"), Qt::DescendingOrder);
|
|
|
|
|
|
|
|
connect (this, &CabrilloLog::impl::modelReset, self_, &CabrilloLog::data_changed);
|
|
|
|
connect (this, &CabrilloLog::impl::dataChanged, [this] (QModelIndex const& tl, QModelIndex const& br) {
|
|
|
|
if (!adding_row_ && !(tl == br)) // ignore single cell changes
|
|
|
|
// as a another change for the
|
|
|
|
// whole row will follow
|
|
|
|
{
|
|
|
|
Q_EMIT self_->data_changed ();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
SQL_error_check (*this, &QSqlTableModel::select);
|
|
|
|
|
2018-11-11 21:11:58 -05:00
|
|
|
SQL_error_check (dupe_query_, &QSqlQuery::prepare,
|
2019-01-21 08:35:18 -05:00
|
|
|
"SELECT "
|
2019-05-30 17:20:09 -04:00
|
|
|
" frequency "
|
2019-01-21 08:35:18 -05:00
|
|
|
" FROM "
|
2019-05-30 18:29:46 -04:00
|
|
|
" cabrillo_log_v2 "
|
2019-01-21 08:35:18 -05:00
|
|
|
" WHERE "
|
2019-05-30 17:20:09 -04:00
|
|
|
" call = :call ");
|
2018-11-11 21:11:58 -05:00
|
|
|
|
|
|
|
SQL_error_check (export_query_, &QSqlQuery::prepare,
|
2019-01-21 08:35:18 -05:00
|
|
|
"SELECT "
|
|
|
|
" frequency"
|
|
|
|
" , \"when\""
|
|
|
|
" , exchange_sent"
|
|
|
|
" , call"
|
|
|
|
" , exchange_rcvd"
|
|
|
|
" FROM "
|
2019-05-30 18:29:46 -04:00
|
|
|
" cabrillo_log_v2 "
|
2019-01-21 08:35:18 -05:00
|
|
|
" ORDER BY "
|
|
|
|
" \"when\"");
|
2019-05-30 17:20:09 -04:00
|
|
|
}
|
2018-12-06 00:41:16 -05:00
|
|
|
|
2019-05-30 17:20:09 -04:00
|
|
|
void CabrilloLog::impl::create_table ()
|
|
|
|
{
|
|
|
|
QSqlQuery query;
|
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
2019-05-30 18:29:46 -04:00
|
|
|
"CREATE TABLE cabrillo_log_v2 ("
|
2019-05-30 17:20:09 -04:00
|
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
|
|
|
|
" frequency INTEGER NOT NULL,"
|
|
|
|
" mode VARCHAR(6) NOT NULL,"
|
|
|
|
" \"when\" DATETIME NOT NULL,"
|
|
|
|
" call VARCHAR(20) NOT NULL,"
|
|
|
|
" exchange_sent VARCHAR(32) NOT NULL,"
|
|
|
|
" exchange_rcvd VARCHAR(32) NOT NULL"
|
|
|
|
")");
|
2018-11-11 21:11:58 -05:00
|
|
|
}
|
|
|
|
|
2019-01-21 17:03:33 -05:00
|
|
|
// frequency here is in kHz
|
|
|
|
QString CabrilloLog::impl::cabrillo_frequency_string (Radio::Frequency frequency) const
|
|
|
|
{
|
|
|
|
QString result;
|
2019-05-30 17:20:09 -04:00
|
|
|
auto band = configuration_->bands ()->find (frequency);
|
2019-01-21 17:03:33 -05:00
|
|
|
if ("1mm" == band) result = "LIGHT";
|
|
|
|
else if ("2mm" == band) result = "241G";
|
|
|
|
else if ("2.5mm" == band) result = "134G";
|
|
|
|
else if ("4mm" == band) result = "75G";
|
|
|
|
else if ("6mm" == band) result = "47G";
|
|
|
|
else if ("1.25cm" == band) result = "24G";
|
|
|
|
else if ("3cm" == band) result = "10G";
|
|
|
|
else if ("6cm" == band) result = "5.7G";
|
|
|
|
else if ("9cm" == band) result = "3.4G";
|
|
|
|
else if ("13cm" == band) result = "2.3G";
|
|
|
|
else if ("23cm" == band) result = "1.2G";
|
|
|
|
else if ("33cm" == band) result = "902";
|
|
|
|
else if ("70cm" == band) result = "432";
|
|
|
|
else if ("1.25m" == band) result = "222";
|
|
|
|
else if ("2m" == band) result = "144";
|
|
|
|
else if ("4m" == band) result = "70";
|
|
|
|
else if ("6m" == band) result = "50";
|
2019-05-30 17:20:09 -04:00
|
|
|
else result = QString::number (frequency / 1000ull);
|
2019-01-21 17:03:33 -05:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-05-30 17:20:09 -04:00
|
|
|
#include "moc_CabrilloLog.cpp"
|
|
|
|
|
|
|
|
CabrilloLog::CabrilloLog (Configuration const * configuration, QObject * parent)
|
|
|
|
: QObject {parent}
|
|
|
|
, m_ {this, configuration}
|
2018-11-11 21:11:58 -05:00
|
|
|
{
|
|
|
|
Q_ASSERT (configuration);
|
|
|
|
}
|
|
|
|
|
|
|
|
CabrilloLog::~CabrilloLog ()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2018-11-25 17:19:41 -05:00
|
|
|
QSqlTableModel * CabrilloLog::model ()
|
2018-11-11 21:11:58 -05:00
|
|
|
{
|
|
|
|
return &*m_;
|
|
|
|
}
|
|
|
|
|
2018-11-22 20:18:39 -05:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
void set_value_maybe_null (QSqlRecord& record, QString const& name, QString const& value)
|
|
|
|
{
|
|
|
|
if (value.size ())
|
|
|
|
{
|
|
|
|
record.setValue (name, value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
record.setNull (name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-30 17:20:09 -04:00
|
|
|
bool CabrilloLog::add_QSO (Frequency frequency, QString const& mode, QDateTime const& when, QString const& call
|
2018-11-11 21:11:58 -05:00
|
|
|
, QString const& exchange_sent, QString const& exchange_received)
|
|
|
|
{
|
|
|
|
auto record = m_->record ();
|
2019-05-30 17:20:09 -04:00
|
|
|
record.setValue ("frequency", frequency);
|
|
|
|
record.setValue ("mode", mode);
|
2018-11-22 20:18:39 -05:00
|
|
|
if (!when.isNull ())
|
|
|
|
{
|
|
|
|
record.setValue ("when", when.toMSecsSinceEpoch () / 1000ull);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
record.setNull ("when");
|
|
|
|
}
|
|
|
|
set_value_maybe_null (record, "call", call);
|
|
|
|
set_value_maybe_null (record, "exchange_sent", exchange_sent);
|
|
|
|
set_value_maybe_null (record, "exchange_rcvd", exchange_received);
|
2018-12-01 21:30:32 -05:00
|
|
|
if (m_->isDirty ())
|
|
|
|
{
|
|
|
|
m_->revert (); // discard any uncommitted changes
|
|
|
|
}
|
2018-12-06 00:41:16 -05:00
|
|
|
m_->setEditStrategy (QSqlTableModel::OnManualSubmit);
|
|
|
|
ConditionalTransaction transaction {*m_};
|
2019-05-30 17:20:09 -04:00
|
|
|
m_->adding_row_ = true;
|
2018-11-25 17:19:41 -05:00
|
|
|
auto ok = m_->insertRecord (-1, record);
|
2018-12-06 00:41:16 -05:00
|
|
|
transaction.submit ();
|
2019-05-30 17:20:09 -04:00
|
|
|
m_->adding_row_ = false;
|
2018-12-06 00:41:16 -05:00
|
|
|
m_->setEditStrategy (QSqlTableModel::OnFieldChange);
|
2018-11-25 17:19:41 -05:00
|
|
|
return ok;
|
2018-11-11 21:11:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CabrilloLog::dupe (Frequency frequency, QString const& call) const
|
|
|
|
{
|
|
|
|
m_->dupe_query_.bindValue (":call", call);
|
|
|
|
SQL_error_check (m_->dupe_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec));
|
2019-05-30 17:20:09 -04:00
|
|
|
auto record = m_->dupe_query_.record ();
|
|
|
|
auto frequency_index = record.indexOf ("frequency");
|
|
|
|
while (m_->dupe_query_.next ())
|
|
|
|
{
|
|
|
|
if (m_->configuration_->bands ()->find (m_->dupe_query_.value (frequency_index).toULongLong ())
|
|
|
|
== m_->configuration_->bands ()->find (frequency))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2018-11-11 21:11:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void CabrilloLog::reset ()
|
|
|
|
{
|
2019-01-21 08:35:18 -05:00
|
|
|
// synchronize model
|
|
|
|
while (m_->canFetchMore ()) m_->fetchMore ();
|
2018-11-11 23:10:57 -05:00
|
|
|
if (m_->rowCount ())
|
|
|
|
{
|
2018-11-25 17:19:41 -05:00
|
|
|
m_->setEditStrategy (QSqlTableModel::OnManualSubmit);
|
2018-11-11 23:10:57 -05:00
|
|
|
ConditionalTransaction transaction {*m_};
|
|
|
|
SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {});
|
|
|
|
transaction.submit ();
|
2018-11-25 17:19:41 -05:00
|
|
|
m_->select (); // to refresh views
|
2018-12-01 21:30:32 -05:00
|
|
|
m_->setEditStrategy (QSqlTableModel::OnFieldChange);
|
2019-05-30 17:20:09 -04:00
|
|
|
Q_EMIT data_changed ();
|
2018-11-11 23:10:57 -05:00
|
|
|
}
|
2018-11-11 21:11:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void CabrilloLog::export_qsos (QTextStream& stream) const
|
|
|
|
{
|
|
|
|
SQL_error_check (m_->export_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec));
|
|
|
|
auto record = m_->export_query_.record ();
|
|
|
|
auto frequency_index = record.indexOf ("frequency");
|
2019-05-30 17:20:09 -04:00
|
|
|
// auto mode_index = record.indexOf ("mode");
|
2018-11-11 21:11:58 -05:00
|
|
|
auto when_index = record.indexOf ("when");
|
|
|
|
auto call_index = record.indexOf ("call");
|
|
|
|
auto sent_index = record.indexOf ("exchange_sent");
|
|
|
|
auto rcvd_index = record.indexOf ("exchange_rcvd");
|
|
|
|
while (m_->export_query_.next ())
|
|
|
|
{
|
|
|
|
auto my_call = m_->configuration_->my_callsign ();
|
|
|
|
stream << QString {"QSO: %1 DG %2 %3 %4 %5 %6\n"}
|
2019-01-21 17:03:33 -05:00
|
|
|
.arg (m_->cabrillo_frequency_string (m_->export_query_.value (frequency_index).value<Radio::Frequency> ()), 5)
|
|
|
|
.arg (QDateTime::fromMSecsSinceEpoch (m_->export_query_.value (when_index).toULongLong () * 1000ull, Qt::UTC).toString ("yyyy-MM-dd hhmm"))
|
2018-11-11 21:11:58 -05:00
|
|
|
.arg (my_call, -12)
|
|
|
|
.arg (m_->export_query_.value (sent_index).toString (), -13)
|
|
|
|
.arg (m_->export_query_.value (call_index).toString (), -12)
|
|
|
|
.arg (m_->export_query_.value (rcvd_index).toString (), -13);
|
|
|
|
}
|
|
|
|
}
|
2019-05-29 18:35:18 -04:00
|
|
|
|
2019-05-30 17:20:09 -04:00
|
|
|
auto CabrilloLog::unique_DXCC_entities (AD1CCty const * countries) const -> worked_set
|
2019-05-29 18:35:18 -04:00
|
|
|
{
|
2019-05-30 18:29:46 -04:00
|
|
|
QSqlQuery q {"SELECT DISTINCT BAND, CALL FROM cabrillo_log_v2"};
|
2019-05-30 17:20:09 -04:00
|
|
|
auto band_index = q.record ().indexOf ("band");
|
2019-05-29 18:35:18 -04:00
|
|
|
auto call_index = q.record ().indexOf ("call");
|
2019-05-30 17:20:09 -04:00
|
|
|
worked_set entities;
|
2019-05-29 18:35:18 -04:00
|
|
|
while (q.next ())
|
|
|
|
{
|
2019-05-30 17:20:09 -04:00
|
|
|
entities << worked_item {q.value (band_index).toString ()
|
|
|
|
, countries->lookup (q.value (call_index).toString ()).primary_prefix};
|
2019-05-29 18:35:18 -04:00
|
|
|
}
|
|
|
|
return entities;
|
|
|
|
}
|