mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-31 04:50:34 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			311 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			10 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_;
 | |
|   bool adding_row_;
 | |
| };
 | |
| 
 | |
| 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 ();
 | |
|         }
 | |
|     });
 | |
| 
 | |
|   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\"");
 | |
| }
 | |
| 
 | |
| 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);
 | |
|   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;
 | |
| }
 | |
| 
 | |
| 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;
 | |
| }
 |