#include "CabrilloLog.hpp" #include #include #include #include #include #include #include #include #include #include #include "Configuration.hpp" #include "Bands.hpp" #include "logbook/AD1CCty.hpp" #include "qt_db_helpers.hpp" #include "pimpl_impl.hpp" class CabrilloLog::impl final : public QSqlTableModel { public: impl (CabrilloLog *, Configuration const *); int columnCount (QModelIndex const& /*index */) const override { 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) { 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 if (Qt::DisplayRole == role) { value = configuration_->bands ()->find (QSqlTableModel::data (index (model_index.row (), fieldIndex ("frequency"))).toULongLong ()); } } else { value = QSqlTableModel::data (model_index, role); if (Qt::DisplayRole == role) { if (model_index.column () == fieldIndex ("frequency")) { value = Radio::frequency_MHz_string (value.value (), 3); // kHz precision } else if (model_index.column () == fieldIndex ("when")) { // adjust date/time to Qt format QLocale locale; value = locale.toString (QDateTime::fromMSecsSinceEpoch (value.toULongLong () * 1000ull, Qt::UTC), locale.dateFormat (QLocale::ShortFormat) + " hh:mm:ss"); } } } return value; } QString cabrillo_frequency_string (Radio::Frequency frequency) const; void create_table (); CabrilloLog * self_; Configuration const * configuration_; QSqlQuery mutable dupe_query_; QSqlQuery mutable export_query_; QSqlQuery mutable qso_count_query_; bool adding_row_; int n_qso(); }; CabrilloLog::impl::impl (CabrilloLog * self, Configuration const * configuration) : self_ {self} , configuration_ {configuration} , adding_row_ {false} { if (!database ().tables ().contains ("cabrillo_log_v2")) { create_table (); } setEditStrategy (QSqlTableModel::OnFieldChange); setTable ("cabrillo_log_v2"); 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 (); } Q_EMIT self_->qso_count_changed(self_->n_qso()); }); SQL_error_check (*this, &QSqlTableModel::select); SQL_error_check (dupe_query_, &QSqlQuery::prepare, "SELECT " " frequency " " FROM " " cabrillo_log_v2 " " WHERE " " call = :call "); SQL_error_check (export_query_, &QSqlQuery::prepare, "SELECT " " frequency" " , \"when\"" " , exchange_sent" " , call" " , exchange_rcvd" " FROM " " cabrillo_log_v2 " " ORDER BY " " \"when\""); SQL_error_check (qso_count_query_, &QSqlQuery::prepare, "SELECT COUNT(*) FROM cabrillo_log_v2"); } void CabrilloLog::impl::create_table () { QSqlQuery query; SQL_error_check (query, static_cast (&QSqlQuery::exec), "CREATE TABLE cabrillo_log_v2 (" " 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" ")"); } // frequency here is in kHz QString CabrilloLog::impl::cabrillo_frequency_string (Radio::Frequency frequency) const { QString result; auto band = configuration_->bands ()->find (frequency); 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"; else result = QString::number (frequency / 1000ull); return result; } #include "moc_CabrilloLog.cpp" CabrilloLog::CabrilloLog (Configuration const * configuration, QObject * parent) : QObject {parent} , m_ {this, configuration} { Q_ASSERT (configuration); } CabrilloLog::~CabrilloLog () { } QSqlTableModel * 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, QString const& mode, QDateTime const& when, QString const& call , QString const& exchange_sent, QString const& exchange_received) { auto record = m_->record (); record.setValue ("frequency", frequency); record.setValue ("mode", mode); 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); if (m_->isDirty ()) { m_->revert (); // discard any uncommitted changes } m_->setEditStrategy (QSqlTableModel::OnManualSubmit); ConditionalTransaction transaction {*m_}; m_->adding_row_ = true; auto ok = m_->insertRecord (-1, record); transaction.submit (); m_->adding_row_ = false; m_->setEditStrategy (QSqlTableModel::OnFieldChange); Q_EMIT this->qso_count_changed(this->n_qso()); return ok; } bool CabrilloLog::dupe (Frequency frequency, QString const& call) const { m_->dupe_query_.bindValue (":call", call); SQL_error_check (m_->dupe_query_, static_cast (&QSqlQuery::exec)); 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; } int CabrilloLog::n_qso() { SQL_error_check (m_->qso_count_query_, static_cast (&QSqlQuery::exec)); m_->qso_count_query_.first(); return m_->qso_count_query_.value(0).toInt(); } void CabrilloLog::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); Q_EMIT data_changed (); } } void CabrilloLog::export_qsos (QTextStream& stream) const { SQL_error_check (m_->export_query_, static_cast (&QSqlQuery::exec)); auto record = m_->export_query_.record (); auto frequency_index = record.indexOf ("frequency"); // auto mode_index = record.indexOf ("mode"); 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"} .arg (m_->cabrillo_frequency_string (m_->export_query_.value (frequency_index).value ()), 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); } } auto CabrilloLog::unique_DXCC_entities (AD1CCty const * countries) const -> worked_set { QSqlQuery q {"SELECT DISTINCT BAND, CALL FROM cabrillo_log_v2"}; auto band_index = q.record ().indexOf ("band"); auto call_index = q.record ().indexOf ("call"); worked_set entities; while (q.next ()) { entities << worked_item {q.value (band_index).toString () , countries->lookup (q.value (call_index).toString ()).primary_prefix}; } return entities; }