mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-11-04 05:50:31 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			327 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
		
			11 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 "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<Radio::Frequency> (), 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<bool (QSqlQuery::*) (QString const&)> (&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<bool (QSqlQuery::*) ()> (&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<bool (QSqlQuery::*) ()> (&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<bool (QSqlQuery::*) ()> (&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<Radio::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);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
}
 |