WSJT-X/models/FoxLog.cpp
Bill Somerville 45b12e6028
Preparation for UI i18n
Re-enabling the WSJT-X i18n  facilities. This allows translation files
to  be created  for languages  that are  automatically used  to lookup
translatable strings. To enable a  new language the language name must
be added to the CMakeLists.txt LANGUAGES list variable in BCP47 format
(i.e. en_US,  en_GB, pt_PT, ...). Do  one build with the  CMake option
UPDATE_TRANSLATIONS enabled  (do not  leave it enabled  as there  is a
danger of loosing existing translated texts), that will create a fresh
translations/wsjtx_<lang>.ts file which  should be immediately checked
in with the  CMakeLists.txt change. The .ts should then  be updated by
the translator using  the Qt Linguist tool to  add translations. Check
in the  updated .ts file  to complete the initial  translation process
for that language.

To  aid translators  their WIP  .ts file  may be  tested by  releasing
(using the  lrelease tool or  from the Linguist  menu) a .qm  file and
placing  that  .qm  file  in the  current  directory  before  starting
WSJT-X. The translations will be used if the system locale matches the
file name.   If the system  locale does not  match the file  name; the
language may be  overridden by setting the  LANG environment variable.
For  example if  a wsjtx_pt_PT.qm  file  is in  the current  directory
WSJT-X will use it for  translation lookups, regardless of the current
system locale setting, if the LANG variable is set to pt_PT or pt-PT.

On MS Windows from a command prompt:

 set LANG=pt_PT
 C:\WSJT\wsjtx\bin\wsjtx

elsewhere:

 LANG=pt_PT wsjtx
2019-06-06 12:56:25 +01:00

249 lines
7.6 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
{
Q_OBJECT
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_;
};
#include "FoxLog.moc"
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;
}