mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-16 09:01:59 -05:00
b79cf0df99
Where tool tips are defined in rich text, equivalent pain test accessible descriptions have been added so that screen readers do not announce HTML tags. Refactored date time delegates to use a simpler default editor via a default item editor factory for QDateTime values, the editor is a standard QDateTimeEdit with a format that includes seconds and renders assuming the time is UTC. Modified the Cabrillo log and Fox log database table models to provide QDateTime items for the edit role of date time fields, and formated date time strings including seconds and assumed as UTC for the display role.
245 lines
7.5 KiB
C++
245 lines
7.5 KiB
C++
#include "FoxLog.hpp"
|
|
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
#include <QString>
|
|
#include <QDateTime>
|
|
#include <QSqlDatabase>
|
|
#include <QSqlTableModel>
|
|
#include <QSqlRecord>
|
|
#include <QSqlError>
|
|
#include <QSqlQuery>
|
|
#include <QTextStream>
|
|
#include <QDebug>
|
|
#include "Configuration.hpp"
|
|
#include "qt_db_helpers.hpp"
|
|
#include "pimpl_impl.hpp"
|
|
|
|
class FoxLog::impl final
|
|
: public QSqlTableModel
|
|
{
|
|
public:
|
|
impl (Configuration const * configuration);
|
|
|
|
QVariant data (QModelIndex const& index, int role) const
|
|
{
|
|
auto value = QSqlTableModel::data (index, role);
|
|
if (index.column () == fieldIndex ("when")
|
|
&& (Qt::DisplayRole == role || Qt::EditRole == role))
|
|
{
|
|
auto t = QDateTime::fromMSecsSinceEpoch (value.toULongLong () * 1000ull, Qt::UTC);
|
|
if (Qt::DisplayRole == role)
|
|
{
|
|
QLocale locale;
|
|
return locale.toString (t, locale.dateFormat (QLocale::ShortFormat) + " hh:mm:ss");
|
|
}
|
|
value = t;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
Configuration const * configuration_;
|
|
QSqlQuery mutable dupe_query_;
|
|
QSqlQuery mutable export_query_;
|
|
};
|
|
|
|
FoxLog::impl::impl (Configuration const * configuration)
|
|
: configuration_ {configuration}
|
|
{
|
|
if (!database ().tables ().contains ("fox_log"))
|
|
{
|
|
QSqlQuery query;
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
|
"CREATE TABLE fox_log ("
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
|
|
" \"when\" DATETIME NOT NULL,"
|
|
" call VARCHAR(20) NOT NULL,"
|
|
" grid VARCHAR(4),"
|
|
" report_sent VARCHAR(3),"
|
|
" report_rcvd VARCHAR(3),"
|
|
" band VARCHAR(6) NOT NULL,"
|
|
" CONSTRAINT no_dupes UNIQUE (call, band)"
|
|
")");
|
|
}
|
|
|
|
SQL_error_check (dupe_query_, &QSqlQuery::prepare,
|
|
"SELECT "
|
|
" COUNT(*) "
|
|
" FROM "
|
|
" fox_log "
|
|
" WHERE "
|
|
" call = :call "
|
|
" AND band = :band");
|
|
|
|
SQL_error_check (export_query_, &QSqlQuery::prepare,
|
|
"SELECT "
|
|
" band"
|
|
" , \"when\""
|
|
" , call"
|
|
" , grid"
|
|
" , report_sent"
|
|
" , report_rcvd "
|
|
" FROM "
|
|
" fox_log "
|
|
" ORDER BY "
|
|
" \"when\"");
|
|
|
|
setEditStrategy (QSqlTableModel::OnFieldChange);
|
|
setTable ("fox_log");
|
|
setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)"));
|
|
setHeaderData (fieldIndex ("call"), Qt::Horizontal, tr ("Call"));
|
|
setHeaderData (fieldIndex ("grid"), Qt::Horizontal, tr ("Grid"));
|
|
setHeaderData (fieldIndex ("report_sent"), Qt::Horizontal, tr ("Sent"));
|
|
setHeaderData (fieldIndex ("report_rcvd"), Qt::Horizontal, tr ("Rcvd"));
|
|
setHeaderData (fieldIndex ("band"), 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);
|
|
|
|
SQL_error_check (*this, &QSqlTableModel::select);
|
|
}
|
|
|
|
FoxLog::FoxLog (Configuration const * configuration)
|
|
: m_ {configuration}
|
|
{
|
|
}
|
|
|
|
FoxLog::~FoxLog ()
|
|
{
|
|
}
|
|
|
|
QSqlTableModel * FoxLog::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 FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& grid
|
|
, QString const& report_sent, QString const& report_received
|
|
, QString const& band)
|
|
{
|
|
auto record = m_->record ();
|
|
if (!when.isNull ())
|
|
{
|
|
record.setValue ("when", when.toMSecsSinceEpoch () / 1000);
|
|
}
|
|
else
|
|
{
|
|
record.setNull ("when");
|
|
}
|
|
set_value_maybe_null (record, "call", call);
|
|
set_value_maybe_null (record, "grid", grid);
|
|
set_value_maybe_null (record, "report_sent", report_sent);
|
|
set_value_maybe_null (record, "report_rcvd", report_received);
|
|
set_value_maybe_null (record, "band", band);
|
|
if (m_->isDirty ())
|
|
{
|
|
m_->revert (); // discard any uncommitted changes
|
|
}
|
|
m_->setEditStrategy (QSqlTableModel::OnManualSubmit);
|
|
ConditionalTransaction transaction {*m_};
|
|
auto ok = m_->insertRecord (-1, record);
|
|
if (ok)
|
|
{
|
|
ok = transaction.submit (false);
|
|
}
|
|
m_->setEditStrategy (QSqlTableModel::OnFieldChange);
|
|
return ok;
|
|
}
|
|
|
|
bool FoxLog::dupe (QString const& call, QString const& band) const
|
|
{
|
|
m_->dupe_query_.bindValue (":call", call);
|
|
m_->dupe_query_.bindValue (":band", band);
|
|
SQL_error_check (m_->dupe_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec));
|
|
m_->dupe_query_.next ();
|
|
return m_->dupe_query_.value (0).toInt ();
|
|
}
|
|
|
|
void FoxLog::reset ()
|
|
{
|
|
// synchronize model
|
|
while (m_->canFetchMore ()) m_->fetchMore ();
|
|
if (m_->rowCount ())
|
|
{
|
|
m_->setEditStrategy (QSqlTableModel::OnManualSubmit);
|
|
ConditionalTransaction transaction {*m_};
|
|
SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {});
|
|
transaction.submit ();
|
|
m_->select (); // to refresh views
|
|
m_->setEditStrategy (QSqlTableModel::OnFieldChange);
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
struct ADIF_field
|
|
{
|
|
explicit ADIF_field (QString const& name, QString const& value)
|
|
: name_ {name}
|
|
, value_ {value}
|
|
{
|
|
}
|
|
|
|
QString name_;
|
|
QString value_;
|
|
};
|
|
|
|
QTextStream& operator << (QTextStream& os, ADIF_field const& field)
|
|
{
|
|
if (field.value_.size ())
|
|
{
|
|
os << QString {"<%1:%2>%3 "}.arg (field.name_).arg (field.value_.size ()).arg (field.value_);
|
|
}
|
|
return os;
|
|
}
|
|
}
|
|
|
|
void FoxLog::export_qsos (QTextStream& out) const
|
|
{
|
|
out << "WSJT-X FT8 DXpedition Mode Fox Log\n<eoh>";
|
|
|
|
SQL_error_check (m_->export_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec));
|
|
auto record = m_->export_query_.record ();
|
|
auto band_index = record.indexOf ("band");
|
|
auto when_index = record.indexOf ("when");
|
|
auto call_index = record.indexOf ("call");
|
|
auto grid_index = record.indexOf ("grid");
|
|
auto sent_index = record.indexOf ("report_sent");
|
|
auto rcvd_index = record.indexOf ("report_rcvd");
|
|
while (m_->export_query_.next ())
|
|
{
|
|
auto when = QDateTime::fromMSecsSinceEpoch (m_->export_query_.value (when_index).toULongLong () * 1000ull, Qt::UTC);
|
|
out << '\n'
|
|
<< ADIF_field {"band", m_->export_query_.value (band_index).toString ()}
|
|
<< ADIF_field {"mode", "FT8"}
|
|
<< ADIF_field {"qso_date", when.toString ("yyyyMMdd")}
|
|
<< ADIF_field {"time_on", when.toString ("hhmmss")}
|
|
<< ADIF_field {"call", m_->export_query_.value (call_index).toString ()}
|
|
<< ADIF_field {"gridsquare", m_->export_query_.value (grid_index).toString ()}
|
|
<< ADIF_field {"rst_sent", m_->export_query_.value (sent_index).toString ()}
|
|
<< ADIF_field {"rst_rcvd", m_->export_query_.value (rcvd_index).toString ()}
|
|
<< ADIF_field {"station_callsign", m_->configuration_->my_callsign ()}
|
|
<< ADIF_field {"my_gridsquare", m_->configuration_->my_grid ()}
|
|
<< ADIF_field {"operator", m_->configuration_->opCall ()}
|
|
<< "<eor>";
|
|
}
|
|
out << endl;
|
|
}
|