WSJT-X/models/CabrilloLog.cpp
Bill Somerville c81b3c8e65 Validate contest QSO details before allowing logging
Basic  validation, must  have  non-empty exchange  sent and  received.
Abstracted log view window widget  behaviour into a base class. Turned
on  auto  resize  to  row  height in  log  view  windows  and  enabled
alternating colours. Convert  empty fields to NULL  when inserting new
log table  rows to signify missing  data. Trap insert row  errors when
adding  to contest  log table  so  that logging  can be  held back  if
constraints are not met. Re-factored  log QSO processing to try insert
row into log table  first and pop up a message  box if constraints are
not met, this pops  up the Log QSO window in case  it was initiated by
an auto log event.
2018-11-23 01:18:39 +00:00

165 lines
5.5 KiB
C++

#include "CabrilloLog.hpp"
#include <stdexcept>
#include <utility>
#include <QString>
#include <QDateTime>
#include <QSqlDatabase>
#include <QSqlTableModel>
#include <QSqlRecord>
#include <QSqlError>
#include <QSqlQuery>
#include <QDataStream>
#include <QDebug>
#include "Configuration.hpp"
#include "Bands.hpp"
#include "qt_db_helpers.hpp"
#include "pimpl_impl.hpp"
class CabrilloLog::impl final
: public QSqlTableModel
{
public:
impl (Configuration const *);
Configuration const * configuration_;
QSqlQuery mutable dupe_query_;
QSqlQuery mutable export_query_;
};
CabrilloLog::impl::impl (Configuration const * configuration)
: QSqlTableModel {}
, configuration_ {configuration}
{
if (!database ().tables ().contains ("cabrillo_log"))
{
QSqlQuery query;
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
"CREATE TABLE cabrillo_log ("
" id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
" frequency INTEGER NOT NULL,"
" \"when\" DATETIME NOT NULL,"
" call VARCHAR(20) NOT NULL,"
" exchange_sent VARCHAR(32) NOT NULL,"
" exchange_rcvd VARCHAR(32) NOT NULL,"
" band VARCHAR(6) NOT NULL"
")");
}
SQL_error_check (dupe_query_, &QSqlQuery::prepare,
"SELECT COUNT(*) FROM cabrillo_log WHERE call = :call AND band = :band");
SQL_error_check (export_query_, &QSqlQuery::prepare,
"SELECT frequency, \"when\", exchange_sent, call, exchange_rcvd FROM cabrillo_log ORDER BY \"when\"");
setEditStrategy (QSqlTableModel::OnManualSubmit);
setTable ("cabrillo_log");
setHeaderData (fieldIndex ("frequency"), Qt::Horizontal, tr ("Freq(kHz)"));
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 (fieldIndex ("band"), Qt::Horizontal, tr ("Band"));
SQL_error_check (*this, &QSqlTableModel::select);
}
CabrilloLog::CabrilloLog (Configuration const * configuration)
: m_ {configuration}
{
Q_ASSERT (configuration);
}
CabrilloLog::~CabrilloLog ()
{
}
QAbstractItemModel * CabrilloLog::model ()
{
return &*m_;
}
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);
}
}
}
bool CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString const& call
, QString const& exchange_sent, QString const& exchange_received)
{
ConditionalTransaction transaction {*m_};
auto record = m_->record ();
record.setValue ("frequency", frequency / 1000ull); // kHz
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);
set_value_maybe_null (record, "band", m_->configuration_->bands ()->find (frequency));
SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record);
if (!transaction.submit (false))
{
transaction.revert ();
return false;
}
return true;
}
bool CabrilloLog::dupe (Frequency frequency, QString const& call) const
{
m_->dupe_query_.bindValue (":call", call);
m_->dupe_query_.bindValue (":band", m_->configuration_->bands ()->find (frequency));
SQL_error_check (m_->dupe_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec));
m_->dupe_query_.next ();
return m_->dupe_query_.value (0).toInt ();
}
void CabrilloLog::reset ()
{
if (m_->rowCount ())
{
ConditionalTransaction transaction {*m_};
SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {});
transaction.submit ();
}
}
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");
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 frequency = m_->export_query_.value (frequency_index).value<Radio::Frequency> ();
auto my_call = m_->configuration_->my_callsign ();
frequency = frequency > 50000000ull ? frequency / 1000ull : frequency;
stream << QString {"QSO: %1 DG %2 %3 %4 %5 %6\n"}
.arg (frequency, 5)
.arg (QDateTime::fromMSecsSinceEpoch (m_->export_query_.value (when_index).toULongLong () * 1000ull, Qt::UTC).toString ("yyyy-MM-dd hhmm"))
.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);
}
}