Priorities for decoded message highlighting and new worked before internal database

Settings option to highlight not worked before entities/grids/calls by
mode. Fix issues with highlighting  decodes and generally refactor the
internal  workings  of  ADIF  and  QSO  recording  for  worked  before
detection.
This commit is contained in:
Bill Somerville 2018-10-25 00:00:19 +01:00
parent c6a3b9a170
commit 8215f3412b
19 changed files with 774 additions and 672 deletions

View File

@ -281,10 +281,9 @@ set (jt9_CXXSRCS
)
set (wsjtx_CXXSRCS
logbook/adif.cpp
logbook/countrydat.cpp
logbook/countriesworked.cpp
logbook/logbook.cpp
logbook/WorkedBefore.cpp
psk_reporter.cpp
Modulator.cpp
Detector.cpp

View File

@ -525,6 +525,7 @@ private:
DecodeHighlightingModel decode_highlighing_model_;
DecodeHighlightingModel next_decode_highlighing_model_;
bool highlight_by_mode_;
int LotW_days_since_upload_;
TransceiverFactory::ParameterPack rig_params_;
@ -708,6 +709,7 @@ bool Configuration::pwrBandTxMemory () const {return m_->pwrBandTxMemory_;}
bool Configuration::pwrBandTuneMemory () const {return m_->pwrBandTuneMemory_;}
LotWUsers const& Configuration::lotw_users () const {return m_->lotw_users_;}
DecodeHighlightingModel const& Configuration::decode_highlighting () const {return m_->decode_highlighing_model_;}
bool Configuration::highlight_by_mode () const {return m_->highlight_by_mode_;}
void Configuration::set_calibration (CalibrationParams params)
{
@ -903,6 +905,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
, station_delete_action_ {tr ("&Delete"), nullptr}
, station_insert_action_ {tr ("&Insert ..."), nullptr}
, station_dialog_ {new StationDialog {&next_stations_, &bands_, this}}
, highlight_by_mode_ {false}
, LotW_days_since_upload_ {0}
, last_port_type_ {TransceiverFactory::Capabilities::none}
, rig_is_dummy_ {false}
@ -1266,6 +1269,7 @@ void Configuration::impl::initialize_models ()
next_stations_.station_list (stations_.station_list ());
next_decode_highlighing_model_.items (decode_highlighing_model_.items ());
ui_->highlight_by_mode_check_box->setChecked (highlight_by_mode_);
ui_->LotW_days_since_upload_spin_box->setValue (LotW_days_since_upload_);
set_rig_invariants ();
@ -1411,6 +1415,7 @@ void Configuration::impl::read_settings ()
stations_.station_list (settings_->value ("stations").value<StationList::Stations> ());
decode_highlighing_model_.items (settings_->value ("DecodeHighlighting", QVariant::fromValue (DecodeHighlightingModel::default_items ())).value<DecodeHighlightingModel::HighlightItems> ());
highlight_by_mode_ = settings_->value("HighlightByMode", false).toBool ();
LotW_days_since_upload_ = settings_->value ("LotWDaysSinceLastUpload", 365).toInt ();
lotw_users_.set_age_constraint (LotW_days_since_upload_);
@ -1524,6 +1529,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("FrequenciesForRegionModes", QVariant::fromValue (frequencies_.frequency_list ()));
settings_->setValue ("stations", QVariant::fromValue (stations_.station_list ()));
settings_->setValue ("DecodeHighlighting", QVariant::fromValue (decode_highlighing_model_.items ()));
settings_->setValue ("HighlightByMode", highlight_by_mode_);
settings_->setValue ("LotWDaysSinceLastUpload", LotW_days_since_upload_);
settings_->setValue ("toRTTY", log_as_RTTY_);
settings_->setValue ("dBtoComments", report_in_comments_);
@ -2026,6 +2032,7 @@ void Configuration::impl::accept ()
decode_highlighing_model_.items (next_decode_highlighing_model_.items ());
Q_EMIT self_->decode_highlighting_changed (decode_highlighing_model_);
}
highlight_by_mode_ = ui_->highlight_by_mode_check_box->isChecked ();
LotW_days_since_upload_ = ui_->LotW_days_since_upload_spin_box->value ();
lotw_users_.set_age_constraint (LotW_days_since_upload_);

View File

@ -176,6 +176,7 @@ public:
bool pwrBandTuneMemory () const;
LotWUsers const& lotw_users () const;
DecodeHighlightingModel const& decode_highlighting () const;
bool highlight_by_mode () const;
struct CalibrationParams
{

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>534</width>
<height>546</height>
<width>527</width>
<height>540</height>
</rect>
</property>
<property name="windowTitle">
@ -2235,6 +2235,20 @@ Right click for insert and delete options.</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QCheckBox" name="highlight_by_mode_check_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Check to indicate new DXCC entities, grid squares, and callsigns per mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Highlight by Mode</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -2313,19 +2327,6 @@ Right click for insert and delete options.</string>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer_8">
<property name="orientation">
@ -2339,6 +2340,19 @@ Right click for insert and delete options.</string>
</property>
</spacer>
</item>
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="advanced_tab">
@ -2905,6 +2919,7 @@ Right click for insert and delete options.</string>
<tabstop>stations_table_view</tabstop>
<tabstop>highlighting_list_view</tabstop>
<tabstop>reset_highlighting_to_defaults_push_button</tabstop>
<tabstop>highlight_by_mode_check_box</tabstop>
<tabstop>LotW_CSV_URL_line_edit</tabstop>
<tabstop>LotW_CSV_fetch_push_button</tabstop>
<tabstop>LotW_days_since_upload_spin_box</tabstop>
@ -2994,12 +3009,12 @@ Right click for insert and delete options.</string>
</connection>
</connections>
<buttongroups>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="split_mode_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
</buttongroups>
</ui>

View File

@ -11,10 +11,12 @@
#include <QMenu>
#include <QAction>
#include <QListIterator>
#include <QRegularExpression>
#include "Configuration.hpp"
#include "LotWUsers.hpp"
#include "DecodeHighlightingModel.hpp"
#include "logbook/logbook.h"
#include "qt_helpers.hpp"
#include "moc_displaytext.cpp"
@ -95,7 +97,7 @@ namespace
{
auto const& item = it.previous ();
auto const& type = std::find (types.begin (), types.end (), item.type_);
if (type != types.end () && *type == item.type_ && item.enabled_)
if (type != types.end () && item.enabled_)
{
if (item.background_.style () != Qt::NoBrush)
{
@ -117,40 +119,40 @@ void DisplayText::appendText(QString const& text, QColor bg, QColor fg
auto cursor = textCursor ();
cursor.movePosition (QTextCursor::End);
auto block_format = cursor.blockFormat ();
auto format = cursor.blockCharFormat ();
format.setFont (char_font_);
format.clearBackground ();
if (bg.isValid ())
{
block_format.setBackground (bg);
}
else
{
block_format.clearBackground ();
}
format.clearForeground ();
if (fg.isValid ())
{
block_format.setForeground (fg);
format.setForeground (fg);
}
else
if (call2.size () && m_config && m_config->lotw_users ().user (call2))
{
block_format.clearForeground ();
}
if (0 == cursor.position ())
{
cursor.setBlockFormat (block_format);
auto char_format = cursor.charFormat ();
char_format.setFont (char_font_);
cursor.setCharFormat (char_format);
}
else
{
cursor.insertBlock (block_format);
auto char_format = cursor.charFormat ();
char_format.clearBackground ();
char_format.clearForeground ();
cursor.setCharFormat (char_format);
QColor bg;
QColor fg;
highlight_types types {DecodeHighlightingModel::Highlight::LotW};
set_colours (m_config, &bg, &fg, types);
if (bg.isValid ()) block_format.setBackground (bg);
if (fg.isValid ()) format.setForeground (fg);
}
if (cursor.position ())
{
cursor.insertBlock (block_format, format);
}
else
{
cursor.setBlockFormat (block_format);
cursor.setBlockCharFormat (format);
}
QTextCharFormat format = cursor.charFormat();
int text_index {0};
auto temp_format = format;
if (call1.size ())
{
auto call_index = text.indexOf (call1);
@ -159,16 +161,16 @@ void DisplayText::appendText(QString const& text, QColor bg, QColor fg
auto pos = highlighted_calls_.find (call1);
if (pos != highlighted_calls_.end ())
{
cursor.insertText(text.left (call_index), format);
cursor.insertText(text.left (call_index));
if (pos.value ().first.isValid ())
{
format.setBackground (pos.value ().first);
temp_format.setBackground (pos.value ().first);
}
if (pos.value ().second.isValid ())
{
format.setForeground (pos.value ().second);
temp_format.setForeground (pos.value ().second);
}
cursor.insertText(text.mid (call_index, call1.size ()), format);
cursor.insertText(text.mid (call_index, call1.size ()), temp_format);
text_index = call_index + call1.size ();
}
}
@ -181,31 +183,21 @@ void DisplayText::appendText(QString const& text, QColor bg, QColor fg
auto pos = highlighted_calls_.find (call2);
if (pos != highlighted_calls_.end ())
{
format.setBackground (bg);
format.setForeground (fg);
temp_format = format;
cursor.insertText(text.mid (text_index, call_index - text_index), format);
if (pos.value ().second.isValid ())
{
format.setBackground (pos.value ().first);
temp_format.setBackground (pos.value ().first);
}
if (pos.value ().second.isValid ())
{
format.setForeground (pos.value ().second);
temp_format.setForeground (pos.value ().second);
}
cursor.insertText(text.mid (call_index, call2.size ()), format);
cursor.insertText(text.mid (call_index, call2.size ()), temp_format);
text_index = call_index + call2.size ();
}
}
}
if (call2.size () && m_config && m_config->lotw_users ().user (call2))
{
QColor bg;
QColor fg;
highlight_types types {DecodeHighlightingModel::Highlight::LotW};
set_colours (m_config, &bg, &fg, types);
if (bg.isValid ()) format.setBackground (bg);
if (fg.isValid ()) format.setForeground (fg);
}
cursor.insertText(text.mid (text_index), format);
// position so viewport scrolled to left
@ -215,8 +207,9 @@ void DisplayText::appendText(QString const& text, QColor bg, QColor fg
document ()->setMaximumBlockCount (document ()->maximumBlockCount ());
}
QString DisplayText::appendWorkedB4(QString message, QString const& callsign, QString grid,
QColor * bg, QColor * fg, LogBook const& logBook, QString currentBand)
QString DisplayText::appendWorkedB4 (QString message, QString const& callsign, QString const& grid,
QColor * bg, QColor * fg, LogBook const& logBook,
QString const& currentBand, QString const& currentMode)
{
// allow for seconds
int padding {message.indexOf (" ") > 4 ? 2 : 0};
@ -238,8 +231,8 @@ QString DisplayText::appendWorkedB4(QString message, QString const& callsign, QS
if(call.length()<3) return message;
if(!call.contains(QRegExp("[0-9]|[A-Z]"))) return message;
logBook.match(/*in*/call,grid,/*out*/countryName,callWorkedBefore,countryWorkedBefore,gridB4);
logBook.match(/*in*/call,grid,/*out*/countryName,callB4onBand,countryB4onBand,gridB4onBand,
logBook.match(/*in*/call,currentMode,grid,/*out*/countryName,callWorkedBefore,countryWorkedBefore,gridB4);
logBook.match(/*in*/call,currentMode,grid,/*out*/countryName,callB4onBand,countryB4onBand,gridB4onBand,
/*in*/ currentBand);
if(grid=="") {
gridB4=true;
@ -247,7 +240,7 @@ QString DisplayText::appendWorkedB4(QString message, QString const& callsign, QS
}
message = message.trimmed ();
QString appendage{""};
QString appendage;
highlight_types types;
// no shortcuts here as some types may be disabled
@ -313,8 +306,9 @@ QString DisplayText::appendWorkedB4(QString message, QString const& callsign, QS
}
void DisplayText::displayDecodedText(DecodedText const& decodedText, QString const& myCall,
QString const& mode,
bool displayDXCCEntity, LogBook const& logBook,
QString currentBand, bool ppfx, bool bCQonly)
QString const& currentBand, bool ppfx, bool bCQonly)
{
m_bPrincipalPrefix=ppfx;
QColor bg;
@ -348,9 +342,17 @@ void DisplayText::displayDecodedText(DecodedText const& decodedText, QString con
if(!dxGrid.contains(grid_regexp)) dxGrid="";
message = message.left (message.indexOf (QChar::Nbsp)); // strip appended info
if (displayDXCCEntity && CQcall)
// if enabled add the DXCC entity and B4 status to the end of the
// preformated text line t1
message = appendWorkedB4 (message, decodedText.CQersCall(), dxGrid, &bg, &fg, logBook, currentBand);
{
// if enabled add the DXCC entity and B4 status to the end of the
// preformated text line t1
auto currentMode = mode;
if ("JT9+JT65" == mode)
{
currentMode = decodedText.isJT65 () ? "JT65" : "JT9";
}
message = appendWorkedB4 (message, decodedText.CQersCall(), dxGrid, &bg, &fg
, logBook, currentBand, currentMode);
}
appendText (message.trimmed (), bg, fg, decodedText.call (), dxCall);
}

View File

@ -8,11 +8,11 @@
#include <QPair>
#include <QString>
#include "logbook/logbook.h"
#include "decodedtext.h"
class QAction;
class Configuration;
class LogBook;
class DisplayText
: public QTextEdit
@ -23,9 +23,9 @@ public:
void set_configuration (Configuration const * configuration) {m_config = configuration;}
void setContentFont (QFont const&);
void insertLineSpacer(QString const&);
void displayDecodedText(DecodedText const& decodedText, QString const& myCall,
bool displayDXCCEntity, LogBook const& logBook,
QString currentBand="", bool ppfx=false, bool bCQonly=false);
void displayDecodedText(DecodedText const& decodedText, QString const& myCall, QString const& mode,
bool displayDXCCEntity, LogBook const& logBook,
QString const& currentBand=QString {}, bool ppfx=false, bool bCQonly=false);
void displayTransmittedText(QString text, QString modeTx, qint32 txFreq, bool bFastMode);
void displayQSY(QString text);
void displayFoxToBeCalled(QString t, QColor bg = QColor {}, QColor fg = QColor {});
@ -45,8 +45,9 @@ private:
Configuration const * m_config;
bool m_bPrincipalPrefix;
QString appendWorkedB4(QString message, QString const& callsign
, QString grid, QColor * bg, QColor * fg
, LogBook const& logBook, QString currentBand);
, QString const& grid, QColor * bg, QColor * fg
, LogBook const& logBook, QString const& currentBand
, QString const& currentMode);
QFont char_font_;
QAction * erase_action_;
QHash<QString, QPair<QColor, QColor>> highlighted_calls_;

377
logbook/WorkedBefore.cpp Normal file
View File

@ -0,0 +1,377 @@
#include "WorkedBefore.hpp"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/key_extractors.hpp>
#include <QByteArray>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include "countrydat.h"
#include "pimpl_impl.hpp"
// worked before set element
struct worked_entry
{
explicit worked_entry (std::string const& call
, std::string const& grid
, std::string const& band
, std::string const& mode
, std::string const& country)
: call {call}
, grid {grid}
, band {band}
, mode {mode}
, country {country}
{
}
std::string call;
std::string grid;
std::string band;
std::string mode;
std::string country;
};
// tags
struct call_mode_band {};
struct call_band {};
struct grid_mode_band {};
struct grid_band {};
struct entity_mode_band {};
struct entity_band {};
// set with multiple ordered unique indexes that allow for efficient
// determination of various categories of worked before status
typedef boost::multi_index::multi_index_container<
worked_entry,
boost::multi_index::indexed_by<
// call+mode+band
boost::multi_index::ordered_unique<
boost::multi_index::tag<call_mode_band>,
boost::multi_index::composite_key<
worked_entry,
boost::multi_index::member<worked_entry, std::string, &worked_entry::call>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::mode>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::band>
>
>,
// call+band
boost::multi_index::ordered_unique<
boost::multi_index::tag<call_band>,
boost::multi_index::composite_key<
worked_entry,
boost::multi_index::member<worked_entry, std::string, &worked_entry::call>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::band>
>
>,
// grid+mode+band
boost::multi_index::ordered_unique<
boost::multi_index::tag<grid_mode_band>,
boost::multi_index::composite_key<
worked_entry,
boost::multi_index::member<worked_entry, std::string, &worked_entry::grid>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::mode>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::band>
>
>,
// grid+band
boost::multi_index::ordered_unique<
boost::multi_index::tag<grid_band>,
boost::multi_index::composite_key<
worked_entry,
boost::multi_index::member<worked_entry, std::string, &worked_entry::grid>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::band>
>
>,
// country+mode+band
boost::multi_index::ordered_unique<
boost::multi_index::tag<entity_mode_band>,
boost::multi_index::composite_key<
worked_entry,
boost::multi_index::member<worked_entry, std::string, &worked_entry::country>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::mode>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::band>
>
>,
// country+band
boost::multi_index::ordered_unique<
boost::multi_index::tag<entity_band>,
boost::multi_index::composite_key<
worked_entry,
boost::multi_index::member<worked_entry, std::string, &worked_entry::country>,
boost::multi_index::member<worked_entry, std::string, &worked_entry::band>
>
>
>
> worked_type;
namespace
{
auto const logFileName = "wsjtx_log.adi";
std::string extractField (QString const& record, QString const& fieldName)
{
int fieldNameIndex = record.indexOf ('<' + fieldName + ':', 0, Qt::CaseInsensitive);
if (fieldNameIndex >=0)
{
int closingBracketIndex = record.indexOf('>',fieldNameIndex);
int fieldLengthIndex = record.indexOf(':',fieldNameIndex); // find the size delimiter
int dataTypeIndex = -1;
if (fieldLengthIndex >= 0)
{
dataTypeIndex = record.indexOf(':',fieldLengthIndex+1); // check for a second : indicating there is a data type
if (dataTypeIndex > closingBracketIndex)
dataTypeIndex = -1; // second : was found but it was beyond the closing >
}
if ((closingBracketIndex > fieldNameIndex) && (fieldLengthIndex > fieldNameIndex) && (fieldLengthIndex< closingBracketIndex))
{
int fieldLengthCharCount = closingBracketIndex - fieldLengthIndex -1;
if (dataTypeIndex >= 0)
fieldLengthCharCount -= 2; // data type indicator is always a colon followed by a single character
QString fieldLengthString = record.mid(fieldLengthIndex+1,fieldLengthCharCount);
int fieldLength = fieldLengthString.toInt();
if (fieldLength > 0)
{
QString field = record.mid(closingBracketIndex+1,fieldLength);
return field.toStdString ();
}
}
}
return "";
}
}
class WorkedBefore::impl final
{
public:
impl ()
: path_ {QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath (logFileName)}
{
}
QString path_;
CountryDat countries_;
worked_type worked_;
};
WorkedBefore::WorkedBefore ()
{
QFile inputFile {m_->path_};
if (inputFile.open (QFile::ReadOnly))
{
QTextStream in(&inputFile);
QString buffer;
bool pre_read {false};
int end_position {-1};
// skip optional header record
do
{
buffer += in.readLine () + '\n';
if (buffer.startsWith (QChar {'<'})) // denotes no header
{
pre_read = true;
}
else
{
end_position = buffer.indexOf ("<EOH>", 0, Qt::CaseInsensitive);
}
}
while (!in.atEnd () && !pre_read && end_position < 0);
if (!pre_read) // found header
{
buffer.remove (0, end_position + 5);
}
while (buffer.size () || !in.atEnd ())
{
do
{
end_position = buffer.indexOf ("<EOR>", 0, Qt::CaseInsensitive);
if (!in.atEnd () && end_position < 0)
{
buffer += in.readLine () + '\n';
}
}
while (!in.atEnd () && end_position < 0);
int record_length {end_position >= 0 ? end_position + 5 : -1};
auto record = buffer.left (record_length).trimmed ();
auto next_record = buffer.indexOf (QChar {'<'}, record_length);
buffer.remove (0, next_record >=0 ? next_record : buffer.size ());
record = record.mid (record.indexOf (QChar {'<'}));
auto call = extractField (record, "CALL");
if (call.size ())
{
m_->worked_.emplace (call
, extractField (record, "GRIDSQUARE").substr (0, 4) // not interested in 6-digit grids
, extractField (record, "BAND")
, extractField (record, "MODE")
, m_->countries_.find (QString::fromStdString (call)).toStdString ());
}
}
}
}
WorkedBefore::~WorkedBefore ()
{
}
QString const& WorkedBefore::path () const
{
return m_->path_;
}
CountryDat const& WorkedBefore::countries () const
{
return m_->countries_;
}
bool WorkedBefore::add (QString const& call
, QString const& grid
, QString const& band
, QString const& mode
, QByteArray const& ADIF_record)
{
if (call.size ())
{
QFile file {m_->path_};
if (!file.open(QIODevice::Text | QIODevice::Append))
{
return false;
}
else
{
QTextStream out {&file};
if (!file.size ())
{
out << "WSJT-X ADIF Export<eoh>" << endl; // new file
}
out << ADIF_record << " <eor>" << endl;
}
m_->worked_.emplace (call.toStdString ()
, grid.toStdString ()
, band.toStdString ()
, mode.toStdString ()
, m_->countries_.find (call).toStdString ());
}
return true;
}
bool WorkedBefore::country_worked (QString const& country, QString const& mode, QString const& band) const
{
if (mode.size ())
{
if (band.size ())
{
return
country.size ()
&& m_->worked_.get<entity_mode_band> ().end ()
!= m_->worked_.get<entity_mode_band> ().find (std::make_tuple (country.toStdString ()
, mode.toStdString ()
, band.toStdString ()));
}
else
{
// partial key lookup
return
country.size ()
&& m_->worked_.get<entity_mode_band> ().end ()
!= m_->worked_.get<entity_mode_band> ().find (std::make_tuple (country.toStdString ()
, mode.toStdString ()));
}
}
else
{
if (band.size ())
{
return
country.size ()
&& m_->worked_.get<entity_band> ().end ()
!= m_->worked_.get<entity_band> ().find (std::make_tuple (country.toStdString ()
, band.toStdString ()));
}
else
{
// partial key lookup
return
country.size ()
&& m_->worked_.get<entity_band> ().end ()
!= m_->worked_.get<entity_band> ().find (std::make_tuple (country.toStdString ()));
}
}
}
bool WorkedBefore::grid_worked (QString const& grid, QString const& mode, QString const& band) const
{
if (mode.size ())
{
if (band.size ())
{
return m_->worked_.get<grid_mode_band> ().end ()
!= m_->worked_.get<grid_mode_band> ().find (std::make_tuple (grid.toStdString ()
, mode.toStdString ()
, band.toStdString ()));
}
else
{
// partial key lookup
return m_->worked_.get<grid_mode_band> ().end ()
!= m_->worked_.get<grid_mode_band> ().find (std::make_tuple (grid.toStdString ()
, mode.toStdString ()));
}
}
else
{
if (band.size ())
{
return m_->worked_.get<grid_band> ().end ()
!= m_->worked_.get<grid_band> ().find (std::make_tuple (grid.toStdString ()
, band.toStdString ()));
}
else
{
// partial key lookup
return m_->worked_.get<grid_band> ().end ()
!= m_->worked_.get<grid_band> ().find (std::make_tuple (grid.toStdString ()));
}
}
}
bool WorkedBefore::call_worked (QString const& call, QString const& mode, QString const& band) const
{
if (mode.size ())
{
if (band.size ())
{
return m_->worked_.get<call_mode_band> ().end ()
!= m_->worked_.get<call_mode_band> ().find (std::make_tuple (call.toStdString ()
, mode.toStdString ()
, band.toStdString ()));
}
else
{
// partial key lookup
return m_->worked_.get<call_mode_band> ().end ()
!= m_->worked_.get<call_mode_band> ().find (std::make_tuple (call.toStdString ()
, mode.toStdString ()));
}
}
else
{
if (band.size ())
{
return m_->worked_.get<call_band> ().end ()
!= m_->worked_.get<call_band> ().find (std::make_tuple (call.toStdString ()
, band.toStdString ()));
}
else
{
// partial key lookup
return m_->worked_.get<call_band> ().end ()
!= m_->worked_.get<call_band> ().find (std::make_tuple (call.toStdString ()));
}
}
}

34
logbook/WorkedBefore.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef WORKWED_BEFORE_HPP_
#define WORKWED_BEFORE_HPP_
#include <boost/core/noncopyable.hpp>
#include "pimpl_h.hpp"
class CountryDat;
class QString;
class QByteArray;
class WorkedBefore final
: private boost::noncopyable
{
public:
explicit WorkedBefore ();
~WorkedBefore ();
QString const& path () const;
CountryDat const& countries () const;
bool add (QString const& call
, QString const& grid
, QString const& band
, QString const& mode
, QByteArray const& ADIF_record);
bool country_worked (QString const& call, QString const& mode, QString const& band) const;
bool grid_worked (QString const& grid, QString const& mode, QString const& band) const;
bool call_worked (QString const& call, QString const& mode, QString const& band) const;
private:
class impl;
pimpl<impl> m_;
};
#endif

View File

@ -1,236 +0,0 @@
#include "adif.h"
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QDebug>
/*
<CALL:4>W1XT<BAND:3>20m<FREQ:6>14.076<GRIDSQUARE:4>DM33<MODE:4>JT65<RST_RCVD:3>-21<RST_SENT:3>-14<QSO_DATE:8>20110422<TIME_ON:6>041712<TIME_OFF:6>042435<TX_PWR:1>4<COMMENT:34>1st JT65A QSO. Him: mag loop 20W<STATION_CALLSIGN:6>VK3ACF<MY_GRIDSQUARE:6>qf22lb<eor>
<CALL:6>IK1SOW<BAND:3>20m<FREQ:6>14.076<GRIDSQUARE:4>JN35<MODE:4>JT65<RST_RCVD:3>-19<RST_SENT:3>-11<QSO_DATE:8>20110422<TIME_ON:6>052501<TIME_OFF:6>053359<TX_PWR:1>3<STATION_CALLSIGN:6>VK3ACF<MY_GRIDSQUARE:6>qf22lb<eor>
<CALL:6:S>W4ABC> ...
*/
void ADIF::init(QString const& filename)
{
_filename = filename;
_data.clear();
_data2.clear();
}
QString ADIF::extractField(QString const& record, QString const& fieldName) const
{
int fieldNameIndex = record.indexOf ('<' + fieldName + ':', 0, Qt::CaseInsensitive);
if (fieldNameIndex >=0)
{
int closingBracketIndex = record.indexOf('>',fieldNameIndex);
int fieldLengthIndex = record.indexOf(':',fieldNameIndex); // find the size delimiter
int dataTypeIndex = -1;
if (fieldLengthIndex >= 0)
{
dataTypeIndex = record.indexOf(':',fieldLengthIndex+1); // check for a second : indicating there is a data type
if (dataTypeIndex > closingBracketIndex)
dataTypeIndex = -1; // second : was found but it was beyond the closing >
}
if ((closingBracketIndex > fieldNameIndex) && (fieldLengthIndex > fieldNameIndex) && (fieldLengthIndex< closingBracketIndex))
{
int fieldLengthCharCount = closingBracketIndex - fieldLengthIndex -1;
if (dataTypeIndex >= 0)
fieldLengthCharCount -= 2; // data type indicator is always a colon followed by a single character
QString fieldLengthString = record.mid(fieldLengthIndex+1,fieldLengthCharCount);
int fieldLength = fieldLengthString.toInt();
if (fieldLength > 0)
{
QString field = record.mid(closingBracketIndex+1,fieldLength);
return field;
}
}
}
return "";
}
void ADIF::load()
{
_data.clear();
_data2.clear();
QFile inputFile(_filename);
if (inputFile.open(QIODevice::ReadOnly))
{
QTextStream in(&inputFile);
QString buffer;
bool pre_read {false};
int end_position {-1};
// skip optional header record
do
{
buffer += in.readLine () + '\n';
if (buffer.startsWith (QChar {'<'})) // denotes no header
{
pre_read = true;
}
else
{
end_position = buffer.indexOf ("<EOH>", 0, Qt::CaseInsensitive);
}
}
while (!in.atEnd () && !pre_read && end_position < 0);
if (!pre_read) // found header
{
buffer.remove (0, end_position + 5);
}
while (buffer.size () || !in.atEnd ())
{
do
{
end_position = buffer.indexOf ("<EOR>", 0, Qt::CaseInsensitive);
if (!in.atEnd () && end_position < 0)
{
buffer += in.readLine () + '\n';
}
}
while (!in.atEnd () && end_position < 0);
int record_length {end_position >= 0 ? end_position + 5 : -1};
auto record = buffer.left (record_length).trimmed ();
auto next_record = buffer.indexOf (QChar {'<'}, record_length);
buffer.remove (0, next_record >=0 ? next_record : buffer.size ());
record = record.mid (record.indexOf (QChar {'<'}));
add (extractField (record, "CALL")
, extractField (record, "GRIDSQUARE")
, extractField (record, "BAND")
, extractField (record, "MODE")
, extractField (record, "QSO_DATE"));
}
inputFile.close ();
}
}
void ADIF::add(QString const& call, QString const& grid, QString const& band,
QString const& mode, QString const& date)
{
QSO q;
q.call = call;
q.grid = grid.left(4); //We only want to test matches to 4-character grids.
q.band = band;
q.mode = mode;
q.date = date;
if(q.call.size ()) {
_data.insert(q.call,q);
_data2.insert(q.grid,q);
// qDebug() << "In the log:" << call << grid << band << mode << date;
}
}
// return true if in the log same band and mode (where JT65 == JT9 == FT8)
bool ADIF::match(QString const& call, QString const& band, QString const& mode) const
{
QList<QSO> qsos;
QRegularExpression grid_regexp {"\\A(?![Rr]{2}73)[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}\\z"};
if(!call.contains(grid_regexp)) {
qsos = _data.values(call);
} else {
qsos = _data2.values(call);
}
// qDebug() << "AA" << call << qsos.size();
if (qsos.size()>0) {
QSO q;
foreach(q,qsos) {
if((band.compare(q.band,Qt::CaseInsensitive) == 0) || (band=="") || (q.band=="")) {
if((
((mode.compare("JT65",Qt::CaseInsensitive)==0) ||
(mode.compare("JT9",Qt::CaseInsensitive)==0) ||
(mode.compare("FT8",Qt::CaseInsensitive)==0))
&&
((q.mode.compare("JT65",Qt::CaseInsensitive)==0) ||
(q.mode.compare("JT9",Qt::CaseInsensitive)==0) ||
(q.mode.compare("FT8",Qt::CaseInsensitive)==0))
)
|| (mode.compare(q.mode,Qt::CaseInsensitive)==0)
|| (mode=="")
|| (q.mode=="")
)
return true;
}
}
}
return false;
}
QList<QString> ADIF::getCallList() const
{
QList<QString> p;
QMultiHash<QString,QSO>::const_iterator i = _data.constBegin();
while (i != _data.constEnd())
{
p << i.key();
++i;
}
return p;
}
int ADIF::getCount() const
{
return _data.size();
}
QByteArray ADIF::QSOToADIF(QString const& hisCall, QString const& hisGrid, QString const& mode,
QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn,
QDateTime const& dateTimeOff, QString const& band, QString const& comments,
QString const& name, QString const& strDialFreq, QString const& m_myCall,
QString const& m_myGrid, QString const& m_txPower, QString const& operator_call,
QString const& xSent, QString const& xRcvd)
{
QString t;
t = "<call:" + QString::number(hisCall.length()) + ">" + hisCall;
t += " <gridsquare:" + QString::number(hisGrid.length()) + ">" + hisGrid;
t += " <mode:" + QString::number(mode.length()) + ">" + mode;
t += " <rst_sent:" + QString::number(rptSent.length()) + ">" + rptSent;
t += " <rst_rcvd:" + QString::number(rptRcvd.length()) + ">" + rptRcvd;
t += " <qso_date:8>" + dateTimeOn.date().toString("yyyyMMdd");
t += " <time_on:6>" + dateTimeOn.time().toString("hhmmss");
t += " <qso_date_off:8>" + dateTimeOff.date().toString("yyyyMMdd");
t += " <time_off:6>" + dateTimeOff.time().toString("hhmmss");
t += " <band:" + QString::number(band.length()) + ">" + band;
t += " <freq:" + QString::number(strDialFreq.length()) + ">" + strDialFreq;
t += " <station_callsign:" + QString::number(m_myCall.length()) + ">" + m_myCall;
t += " <my_gridsquare:" + QString::number(m_myGrid.length()) + ">" + m_myGrid;
if(m_txPower!="") t += " <tx_pwr:" + QString::number(m_txPower.length()) + ">" + m_txPower;
if(comments!="") t += " <comment:" + QString::number(comments.length()) + ">" + comments;
if(name!="") t += " <name:" + QString::number(name.length()) + ">" + name;
if(operator_call!="") t+=" <operator:" + QString::number(operator_call.length()) + ">" + operator_call;
if(xRcvd!="") {
QString t1="";
if(xRcvd.split(" ").size()==2) t1=xRcvd.split(" ").at(1);
if(t1.toInt()>0) {
t += " <SRX:" + QString::number(t1.length()) + ">" + t1;
} else {
t += " <STATE:" + QString::number(t1.length()) + ">" + t1;
}
}
return t.toLatin1();
}
// open ADIF file and append the QSO details. Return true on success
bool ADIF::addQSOToFile(QByteArray const& ADIF_record)
{
QFile f2(_filename);
if (!f2.open(QIODevice::Text | QIODevice::Append))
return false;
else
{
QTextStream out(&f2);
if (f2.size()==0)
out << "WSJT-X ADIF Export<eoh>" << endl; // new file
out << ADIF_record << " <eor>" << endl;
f2.close();
}
return true;
}

View File

@ -1,56 +0,0 @@
/*
* Reads an ADIF log file into memory
* Searches log for call, band and mode
* VK3ACF July 2013
*/
#ifndef __ADIF_H
#define __ADIF_H
#if defined (QT5)
#include <QList>
#include <QString>
#include <QMultiHash>
#include <QRegularExpression>
#else
#include <QtGui>
#endif
class QDateTime;
class ADIF
{
public:
void init(QString const& filename);
void load();
void add(QString const& call, const QString &grid, QString const& band, QString const& mode,
QString const& date);
bool match(QString const& call, QString const& band, QString const& mode) const;
QList<QString> getCallList() const;
int getCount() const;
// open ADIF file and append the QSO details. Return true on success
bool addQSOToFile(QByteArray const& ADIF_record);
QByteArray QSOToADIF(QString const& hisCall, QString const& hisGrid, QString const& mode,
QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn,
QDateTime const& dateTimeOff, QString const& band, QString const& comments,
QString const& name, QString const& strDialFreq, QString const& m_myCall,
QString const& m_myGrid, QString const& m_txPower, QString const& operator_call,
QString const& xSent, QString const& xRcvd);
private:
struct QSO
{
QString call,grid,band,mode,date;
};
QMultiHash<QString, QSO> _data;
QMultiHash<QString, QSO> _data2;
QString _filename;
QString extractField(QString const& line, QString const& fieldName) const;
};
#endif

View File

@ -1,42 +1,24 @@
#include "countriesworked.h"
void CountriesWorked::init(const QStringList countryNames)
{
_data.clear();
foreach(QString name,countryNames)
_data.insert(name,false);
}
#include <set>
void CountriesWorked::setAsWorked(const QString countryName)
{
if (_data.contains(countryName))
_data.insert(countryName,true);
}
bool CountriesWorked::getHasWorked(const QString countryName) const
{
if (_data.contains(countryName))
return _data.value(countryName);
#include "pimpl_impl.hpp"
return false;
}
int CountriesWorked::getWorkedCount() const
class CountriesWorkedimpl final
{
int count = 0;
foreach (bool value,_data)
if (value)
count += 1;
return count;
}
int CountriesWorked::getSize() const
public:
// element is country-name+band where the '+' is actual a character
// which allows an efficient lower_bound() search for country-name+
// to check for ATNOs
std::set<QString> worked_;
};
void CountriesWorked::add (QString const& country, QString const& band)
{
return _data.count();
m_->worked_.insert (country + '+' + band);
}
bool CountriesWorked::contains (QString const& country, QString const& band) const
{
return m_->worked_.end () != m_->worked_.lower_bound (country + '+' + band);
}

View File

@ -1,29 +1,24 @@
/*
* maintains a list of country names that have been worked
* VK3ACF July 2013
*/
#ifndef COUNTRIES_WORKDED_H_
#define COUNTRIES_WORKDED_H_
#ifndef __COUNTRIESWORKDED_H
#define __COUNTRIESWORKDED_H
#include <QList>
#include <QString>
#include <QStringList>
#include <QHash>
#include "pimpl_h.hpp"
class CountriesWorked
class QStringList;
class CountriesWorked final
{
public:
void init(const QStringList countryNames);
void setAsWorked(const QString countryName);
bool getHasWorked(const QString countryName) const;
int getWorkedCount() const;
int getSize() const;
explicit CountriesWorked (QStringList const& countryNames);
~CountriesWorked ();
void add (QString const& country, QString const& band);
bool contains (QString const& country, QString const& band = QString {}) const;
private:
QHash<QString, bool> _data;
class impl;
pimpl<impl> m_;
};
#endif

View File

@ -16,18 +16,61 @@
#include "countrydat.h"
#include <QDir>
#include <QStandardPaths>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include "Radio.hpp"
void CountryDat::init(const QString filename)
namespace
{
_filename = filename;
_data.clear();
auto countryFileName = "cty.dat";
}
QString CountryDat::_extractName(const QString line) const
CountryDat::CountryDat ()
{
QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::DataLocation)};
QFile file {dataPath.exists (countryFileName)
? dataPath.absoluteFilePath (countryFileName) // user override
: QString {":/"} + countryFileName}; // or original in
// the resources FS
if (file.open (QFile::ReadOnly))
{
QTextStream in {&file};
while (!in.atEnd ())
{
QString line1 = in.readLine ();
if (!in.atEnd ())
{
QString line2 = in.readLine ();
auto name = extractName (line1);
if (name.length () > 0)
{
auto const& continent = line1.mid (36,2);
auto principalPrefix = line1.mid (69,4);
principalPrefix = principalPrefix.mid (0, principalPrefix.indexOf (":"));
name += "; " + principalPrefix + "; " + continent;
bool more {true};
QStringList prefixes;
while (more)
{
QStringList p = extractPrefix (line2, more);
prefixes += p;
if (more) line2 = in.readLine ();
}
Q_FOREACH (auto const& p, prefixes)
{
if (p.length() > 0) data_.insert (p, name);
}
}
}
}
}
}
QString CountryDat::extractName (QString const& line) const
{
int s1 = line.indexOf(':');
if (s1>=0)
@ -38,7 +81,7 @@ QString CountryDat::_extractName(const QString line) const
return "";
}
void CountryDat::_removeBrackets(QString &line, const QString a, const QString b) const
void CountryDat::removeBrackets(QString& line, QString const& a, QString const& b) const
{
int s1 = line.indexOf(a);
while (s1 >= 0)
@ -49,15 +92,15 @@ void CountryDat::_removeBrackets(QString &line, const QString a, const QString b
}
}
QStringList CountryDat::_extractPrefix(QString &line, bool &more) const
QStringList CountryDat::extractPrefix(QString& line, bool& more) const
{
line = line.remove(" \n");
line = line.replace(" ","");
_removeBrackets(line,"(",")");
_removeBrackets(line,"[","]");
_removeBrackets(line,"<",">");
_removeBrackets(line,"~","~");
removeBrackets(line,"(",")");
removeBrackets(line,"[","]");
removeBrackets(line,"<",">");
removeBrackets(line,"~","~");
int s1 = line.indexOf(';');
more = true;
@ -72,73 +115,24 @@ QStringList CountryDat::_extractPrefix(QString &line, bool &more) const
return r;
}
void CountryDat::load()
{
_data.clear();
_countryNames.clear(); //used by countriesWorked
QFile inputFile(_filename);
if (inputFile.open(QIODevice::ReadOnly))
{
QTextStream in(&inputFile);
while ( !in.atEnd() )
{
QString line1 = in.readLine();
if ( !in.atEnd() )
{
QString line2 = in.readLine();
QString name = _extractName(line1);
if (name.length()>0)
{
QString continent=line1.mid(36,2);
QString principalPrefix=line1.mid(69,4);
int i1=principalPrefix.indexOf(":");
if(i1>0) principalPrefix=principalPrefix.mid(0,i1);
name += "; " + principalPrefix + "; " + continent;
_countryNames << name;
bool more = true;
QStringList prefixs;
while (more)
{
QStringList p = _extractPrefix(line2,more);
prefixs += p;
if (more)
line2 = in.readLine();
}
QString p;
foreach(p,prefixs)
{
if (p.length() > 0)
_data.insert(p,name);
}
}
}
}
inputFile.close();
}
}
// return country name else ""
QString CountryDat::find(QString call) const
QString CountryDat::find (QString const& call) const
{
call = call.toUpper ();
auto const& search_string = call.toUpper ();
// check for exact match first
if (_data.contains ("=" + call))
if (data_.contains ("=" + search_string))
{
return fixup (_data.value ("=" + call), call);
return fixup (data_.value ("=" + search_string), search_string);
}
auto prefix = Radio::effective_prefix (call);
auto prefix = Radio::effective_prefix (search_string);
auto match_candidate = prefix;
while (match_candidate.size () >= 1)
{
if (_data.contains (match_candidate))
if (data_.contains (match_candidate))
{
return fixup (_data.value (match_candidate), prefix);
return fixup (data_.value (match_candidate), prefix);
}
match_candidate = match_candidate.left (match_candidate.size () - 1);
}

View File

@ -4,33 +4,27 @@
* VK3ACF July 2013
*/
#ifndef COUNTRY_DAT_H_
#define COUNTRY_DAT_H_
#ifndef __COUNTRYDAT_H
#define __COUNTRYDAT_H
#include <boost/core/noncopyable.hpp>
#include <QString>
#include <QStringList>
#include <QHash>
class CountryDat
class CountryDat final
: private boost::noncopyable
{
public:
void init(const QString filename);
void load();
QString find(QString prefix) const; // return country name or ""
QStringList getCountryNames() const { return _countryNames; };
public:
CountryDat ();
QString find (QString const& call) const; // return country name or ""
private:
QString _extractName(const QString line) const;
void _removeBrackets(QString &line, const QString a, const QString b) const;
QStringList _extractPrefix(QString &line, bool &more) const;
QString extractName (QString const& line) const;
void removeBrackets (QString& line, QString const& a, QString const& b) const;
QStringList extractPrefix (QString& line, bool& more) const;
QString fixup (QString country, QString const& call) const;
QString _filename;
QStringList _countryNames;
QHash<QString, QString> _data;
QHash<QString, QString> data_;
};
#endif

View File

@ -1,105 +1,74 @@
#include "logbook.h"
#include <QDateTime>
#include <QDebug>
#include <QFontMetrics>
#include <QStandardPaths>
#include <QDir>
#include "countrydat.h"
#include "Configuration.hpp"
namespace
LogBook::LogBook (Configuration const * configuration)
: config_ {configuration}
{
auto logFileName = "wsjtx_log.adi";
auto countryFileName = "cty.dat";
}
void LogBook::init()
void LogBook::match (QString const& call, QString const& mode, QString const& grid,
QString &countryName,
bool &callWorkedBefore,
bool &countryWorkedBefore,
bool &gridWorkedBefore,
QString const& band) const
{
QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::DataLocation)};
QString countryDataFilename;
if (dataPath.exists (countryFileName))
if (call.length() > 0)
{
// User override
countryDataFilename = dataPath.absoluteFilePath (countryFileName);
}
else
{
countryDataFilename = QString {":/"} + countryFileName;
}
_countries.init(countryDataFilename);
_countries.load();
_worked.init(_countries.getCountryNames());
_log.init(dataPath.absoluteFilePath (logFileName));
_log.load();
_setAlreadyWorkedFromLog();
/*
int QSOcount = _log.getCount();
int count = _worked.getWorkedCount();
qDebug() << QSOcount << "QSOs and" << count << "countries worked in file" << logFilename;
*/
// QString call = "ok1ct";
// QString countryName;
// bool callWorkedBefore,countryWorkedBefore;
// match(/*in*/call,grid, /*out*/ countryName,callWorkedBefore,countryWorkedBefore);
// qDebug() << countryName;
}
void LogBook::_setAlreadyWorkedFromLog()
{
QList<QString> calls = _log.getCallList();
QString c;
foreach(c,calls)
{
QString countryName = _countries.find(c);
if (countryName.length() > 0)
{
_worked.setAsWorked(countryName);
//qDebug() << countryName << " worked " << c;
}
auto const& mode_to_check = (config_ && !config_->highlight_by_mode ()) ? QString {} : mode;
callWorkedBefore = worked_before_.call_worked (call, mode_to_check, band);
gridWorkedBefore = worked_before_.grid_worked(grid, mode_to_check, band);
countryName = worked_before_.countries ().find (call);
countryWorkedBefore = worked_before_.country_worked (countryName, mode_to_check, band);
}
}
void LogBook::match(/*in*/const QString call,QString grid,
/*out*/ QString &countryName,
bool &callWorkedBefore,
bool &countryWorkedBefore,
bool &gridWorkedBefore,
QString currentBand) const
bool LogBook::add (QString const& call
, QString const& grid
, QString const& band
, QString const& mode
, QByteArray const& ADIF_record)
{
// if(currentBand=="") qDebug() << "aa" << grid;
// if(currentBand!="") qDebug() << "bb" << grid << currentBand;
return worked_before_.add (call, grid, band, mode, ADIF_record);
}
if (call.length() > 0) {
QString currentMode = "JT9"; // JT65 == JT9 == FT8 in ADIF::match()
// QString currentBand = ""; // match any band
callWorkedBefore = _log.match(call,currentBand,currentMode);
gridWorkedBefore = _log.match(grid,currentBand,currentMode);
countryName = _countries.find(call);
if (countryName.length() > 0) { // country was found
countryWorkedBefore = _worked.getHasWorked(countryName);
QByteArray LogBook::QSOToADIF (QString const& hisCall, QString const& hisGrid, QString const& mode,
QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn,
QDateTime const& dateTimeOff, QString const& band, QString const& comments,
QString const& name, QString const& strDialFreq, QString const& myCall,
QString const& myGrid, QString const& txPower, QString const& operator_call,
QString const& xSent, QString const& xRcvd)
{
QString t;
t = "<call:" + QString::number(hisCall.length()) + ">" + hisCall;
t += " <gridsquare:" + QString::number(hisGrid.length()) + ">" + hisGrid;
t += " <mode:" + QString::number(mode.length()) + ">" + mode;
t += " <rst_sent:" + QString::number(rptSent.length()) + ">" + rptSent;
t += " <rst_rcvd:" + QString::number(rptRcvd.length()) + ">" + rptRcvd;
t += " <qso_date:8>" + dateTimeOn.date().toString("yyyyMMdd");
t += " <time_on:6>" + dateTimeOn.time().toString("hhmmss");
t += " <qso_date_off:8>" + dateTimeOff.date().toString("yyyyMMdd");
t += " <time_off:6>" + dateTimeOff.time().toString("hhmmss");
t += " <band:" + QString::number(band.length()) + ">" + band;
t += " <freq:" + QString::number(strDialFreq.length()) + ">" + strDialFreq;
t += " <station_callsign:" + QString::number(myCall.length()) + ">" + myCall;
t += " <my_gridsquare:" + QString::number(myGrid.length()) + ">" + myGrid;
if(txPower!="") t += " <tx_pwr:" + QString::number(txPower.length()) + ">" + txPower;
if(comments!="") t += " <comment:" + QString::number(comments.length()) + ">" + comments;
if(name!="") t += " <name:" + QString::number(name.length()) + ">" + name;
if(operator_call!="") t+=" <operator:" + QString::number(operator_call.length()) + ">" + operator_call;
if(xRcvd!="") {
QString t1="";
if(xRcvd.split(" ").size()==2) t1=xRcvd.split(" ").at(1);
if(t1.toInt()>0) {
t += " <SRX:" + QString::number(t1.length()) + ">" + t1;
} else {
countryName = "where?"; //error: prefix not found
countryWorkedBefore = false;
t += " <STATE:" + QString::number(t1.length()) + ">" + t1;
}
// qDebug() << "Logbook:" << call << currentBand << callWorkedBefore << countryName << countryWorkedBefore;
}
return t.toLatin1();
}
void LogBook::addAsWorked(const QString call, const QString grid, const QString band,
const QString mode, const QString date)
{
//qDebug() << "adding " << call << " as worked";
_log.add(call,grid,band,mode,date);
QString countryName = _countries.find(call);
if (countryName.length() > 0)
_worked.setAsWorked(countryName);
}

View File

@ -3,37 +3,43 @@
* VK3ACF July 2013
*/
#ifndef LOGBOOK_H
#define LOGBOOK_H
#ifndef LOG_BOOK_H_
#define LOG_BOOK_H_
#include <boost/core/noncopyable.hpp>
#include <QString>
#include <QFont>
#include "countrydat.h"
#include "countriesworked.h"
#include "adif.h"
#include "WorkedBefore.hpp"
class QDir;
class Configuration;
class QByteArray;
class QDateTime;
class LogBook
class LogBook final
: private boost::noncopyable
{
public:
void init();
void match(/*in*/ const QString call, QString grid,
/*out*/ QString &countryName, bool &callWorkedBefore, bool &countryWorkedBefore,
bool &gridWorkedBefore, QString currentBand="") const;
void addAsWorked(const QString call, const QString grid, const QString band,
const QString mode, const QString date);
private:
CountryDat _countries;
CountriesWorked _worked;
ADIF _log;
void _setAlreadyWorkedFromLog();
public:
LogBook (Configuration const *);
QString const& path () const {return worked_before_.path ();}
bool add (QString const& call
, QString const& grid
, QString const& band
, QString const& mode
, QByteArray const& ADIF_record);
CountryDat const& countries () const {return worked_before_.countries ();}
void match (QString const& call, QString const& mode, QString const& grid,
QString &countryName, bool &callWorkedBefore, bool &countryWorkedBefore,
bool &gridWorkedBefore, QString const& currentBand = QString {}) const;
static QByteArray QSOToADIF (QString const& hisCall, QString const& hisGrid, QString const& mode,
QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn,
QDateTime const& dateTimeOff, QString const& band, QString const& comments,
QString const& name, QString const& strDialFreq, QString const& myCall,
QString const& m_myGrid, QString const& m_txPower, QString const& operator_call,
QString const& xSent, QString const& xRcvd);
private:
Configuration const * config_;
WorkedBefore worked_before_;
};
#endif // LOGBOOK_H
#endif

View File

@ -5,9 +5,8 @@
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include <QUdpSocket>
#include "logbook/adif.h"
#include "logbook/logbook.h"
#include "MessageBox.hpp"
#include "Configuration.hpp"
#include "Bands.hpp"
@ -120,33 +119,8 @@ void LogQSO::accept()
m_comments=comments;
QString strDialFreq(QString::number(m_dialFreq / 1.e6,'f',6));
operator_call = ui->loggedOperator->text();
//Log this QSO to ADIF file "wsjtx_log.adi"
QString filename = "wsjtx_log.adi"; // TODO allow user to set
ADIF adifile;
auto adifilePath = QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath ("wsjtx_log.adi");
adifile.init(adifilePath);
QByteArray ADIF {adifile.QSOToADIF (hisCall, hisGrid, mode, rptSent, rptRcvd,
m_dateTimeOn, m_dateTimeOff, band, comments, name, strDialFreq, m_myCall,
m_myGrid, m_txPower, operator_call, m_xSent, m_xRcvd)};
if (!adifile.addQSOToFile (ADIF))
{
MessageBox::warning_message (this, tr ("Log file error"),
tr ("Cannot open \"%1\"").arg (adifilePath));
}
// Log to N1MM Logger
if (m_config->broadcast_to_n1mm() && m_config->valid_n1mm_info()) {
const QHostAddress n1mmhost = QHostAddress(m_config->n1mm_server_name());
QUdpSocket _sock;
auto rzult = _sock.writeDatagram (ADIF + " <eor>", n1mmhost, quint16(m_config->n1mm_server_port()));
if (rzult == -1) {
MessageBox::warning_message (this, tr ("Error sending log to N1MM"),
tr ("Write returned \"%1\"").arg (rzult));
}
}
//Log this QSO to file "wsjtx.log"
//Log this QSO to file "wsjtx.log"
static QFile f {QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath ("wsjtx.log")};
if(!f.open(QIODevice::Text | QIODevice::Append)) {
MessageBox::warning_message (this, tr ("Log file error"),
@ -165,8 +139,38 @@ void LogQSO::accept()
f.close();
}
//Clean up and finish logging
Q_EMIT acceptQSO (m_dateTimeOff, hisCall, hisGrid, m_dialFreq, mode, rptSent, rptRcvd, m_txPower, comments, name,m_dateTimeOn, operator_call, m_myCall, m_myGrid, ADIF);
//Clean up and finish logging
Q_EMIT acceptQSO (m_dateTimeOff
, hisCall
, hisGrid
, m_dialFreq
, mode
, rptSent
, rptRcvd
, m_txPower
, comments
, name
, m_dateTimeOn
, operator_call
, m_myCall
, m_myGrid
, LogBook::QSOToADIF (hisCall
, hisGrid
, mode
, rptSent
, rptRcvd
, m_dateTimeOn
, m_dateTimeOff
, band
, comments
, name
, strDialFreq
, m_myCall
, m_myGrid
, m_txPower
, operator_call
, m_xSent
, m_xRcvd));
QDialog::accept();
}

View File

@ -2,11 +2,7 @@
#ifndef LogQSO_H
#define LogQSO_H
#ifdef QT5
#include <QtWidgets>
#else
#include <QtGui>
#endif
#include <QDialog>
#include <QString>
#include <QScopedPointer>

View File

@ -24,6 +24,7 @@
#include <QAction>
#include <QActionGroup>
#include <QSplashScreen>
#include <QUdpSocket>
#include "revision_utils.hpp"
#include "qt_helpers.hpp"
@ -61,6 +62,7 @@
#include "ExchangeValidator.hpp"
#include "EqualizationToolsDialog.hpp"
#include "LotWUsers.hpp"
#include "logbook/countrydat.h"
#include "ui_mainwindow.h"
#include "moc_mainwindow.cpp"
@ -353,6 +355,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
},
m_sfx {"P", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A"},
mem_jt9 {shdmem},
m_logBook {&m_config},
m_msAudioOutputBuffered (0u),
m_framesAudioInputBuffered (RX_SAMPLE_RATE / 10),
m_downSampleFactor (downSampleFactor),
@ -1296,7 +1299,7 @@ void MainWindow::dataSink(qint64 frames)
freqcal_(&dec_data.d2[0],&k,&nkhz,&RxFreq,&ftol,&line[0],80);
QString t=QString::fromLatin1(line);
DecodedText decodedtext {t};
ui->decodedTextBrowser->displayDecodedText (decodedtext,m_baseCall,m_config.DXCC(),
ui->decodedTextBrowser->displayDecodedText (decodedtext,m_baseCall,m_mode,m_config.DXCC(),
m_logBook,m_currentBand, m_config.ppfx());
if (ui->measure_check_box->isChecked ()) {
// Append results text to file "fmt.all".
@ -1538,7 +1541,7 @@ void MainWindow::fastSink(qint64 frames)
if(bmsk144 and (line[0]!=0)) {
QString message {QString::fromLatin1 (line)};
DecodedText decodedtext {message.replace (QChar::LineFeed, "")};
ui->decodedTextBrowser->displayDecodedText (decodedtext,m_baseCall,m_config.DXCC(),
ui->decodedTextBrowser->displayDecodedText (decodedtext,m_baseCall,m_mode,m_config.DXCC(),
m_logBook,m_currentBand,m_config.ppfx());
m_bDecoded=true;
auto_sequence (decodedtext, ui->sbFtol->value (), std::numeric_limits<unsigned>::max ());
@ -2821,7 +2824,7 @@ void::MainWindow::fast_decode_done()
//Left (Band activity) window
DecodedText decodedtext {message.replace (QChar::LineFeed, "")};
if(!m_bFastDone) {
ui->decodedTextBrowser->displayDecodedText (decodedtext,m_baseCall,m_config.DXCC(),
ui->decodedTextBrowser->displayDecodedText (decodedtext,m_baseCall,m_mode,m_config.DXCC(),
m_logBook,m_currentBand,m_config.ppfx());
}
@ -2968,12 +2971,12 @@ void MainWindow::readFromStdout() //readFromStdout
if(!m_bDisplayedOnce) {
// This hack sets the font. Surely there's a better way!
DecodedText dt{"."};
ui->decodedTextBrowser->displayDecodedText(dt,m_baseCall,m_config.DXCC(),
ui->decodedTextBrowser->displayDecodedText(dt,m_baseCall,m_mode,m_config.DXCC(),
m_logBook,m_currentBand,m_config.ppfx());
m_bDisplayedOnce=true;
}
} else {
ui->decodedTextBrowser->displayDecodedText(decodedtext0,m_baseCall,m_config.DXCC(),
ui->decodedTextBrowser->displayDecodedText(decodedtext0,m_baseCall,m_mode,m_config.DXCC(),
m_logBook,m_currentBand,m_config.ppfx(),
(ui->cbCQonly->isVisible() and ui->cbCQonly->isChecked()));
}
@ -3008,7 +3011,7 @@ void MainWindow::readFromStdout() //readFromStdout
if (bDisplayRight) {
// This msg is within 10 hertz of our tuned frequency, or a JT4 or JT65 avg,
// or contains MyCall
ui->decodedTextBrowser2->displayDecodedText(decodedtext0,m_baseCall,m_config.DXCC(),
ui->decodedTextBrowser2->displayDecodedText(decodedtext0,m_baseCall,m_mode,m_config.DXCC(),
m_logBook,m_currentBand,m_config.ppfx());
if(m_mode!="JT4") {
@ -4536,7 +4539,7 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie
QString s2 = message.string ().trimmed();
if (s1!=s2 and !message.isTX()) {
if (!s2.contains(m_baseCall) or m_mode=="MSK144") { // Taken care of elsewhere if for_us and slow mode
ui->decodedTextBrowser2->displayDecodedText(message, m_baseCall,m_config.DXCC(),
ui->decodedTextBrowser2->displayDecodedText(message, m_baseCall,m_mode,m_config.DXCC(),
m_logBook,m_currentBand,m_config.ppfx());
}
m_QSOText = s2;
@ -5294,11 +5297,30 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call,
, QString const& my_call, QString const& my_grid, QByteArray const& ADIF)
{
QString date = QSO_date_on.toString("yyyyMMdd");
m_logBook.addAsWorked (m_hisCall,grid,m_config.bands()->find(m_freqNominal),m_modeTx,date);
if (!m_logBook.add (m_hisCall, grid, m_config.bands()->find(m_freqNominal), m_modeTx, ADIF))
{
MessageBox::warning_message (this, tr ("Log file error"),
tr ("Cannot open \"%1\"").arg (m_logBook.path ()));
}
m_messageClient->qso_logged (QSO_date_off, call, grid, dial_freq, mode, rpt_sent, rpt_received, tx_power, comments, name, QSO_date_on, operator_call, my_call, my_grid);
m_messageClient->qso_logged (QSO_date_off, call, grid, dial_freq, mode, rpt_sent, rpt_received
, tx_power, comments, name, QSO_date_on, operator_call, my_call, my_grid);
m_messageClient->logged_ADIF (ADIF);
if (m_config.clear_DX () and !m_config.bHound()) clearDX ();
// Log to N1MM Logger
if (m_config.broadcast_to_n1mm () && m_config.valid_n1mm_info ())
{
QUdpSocket sock;
if (-1 == sock.writeDatagram (ADIF + " <eor>"
, QHostAddress {m_config.n1mm_server_name ()}
, m_config.n1mm_server_port ()))
{
MessageBox::warning_message (this, tr ("Error sending log to N1MM"),
tr ("Write returned \"%1\"").arg (sock.errorString ()));
}
}
if (m_config.clear_DX () and !m_config.bHound ()) clearDX ();
m_dateTimeQSOOn = QDateTime {};
}
@ -6154,7 +6176,7 @@ void MainWindow::vhfWarning()
void MainWindow::enable_DXCC_entity (bool on)
{
if (on and !m_mode.startsWith ("WSPR") and m_mode!="Echo") {
m_logBook.init(); // re-read the log and cty.dat files
//m_logBook.init(); // re-read the log and cty.dat files
// ui->gridLayout->setColumnStretch(0,55); // adjust proportions of text displays
// ui->gridLayout->setColumnStretch(1,45);
} else {
@ -7915,12 +7937,8 @@ void MainWindow::houndCallers()
if(!ui->textBrowser4->toPlainText().contains(paddedHoundCall)) {
if(m_loggedByFox[houndCall].contains(m_lastBand)) continue; //already logged on this band
if(m_foxQSO.contains(houndCall)) continue; //still in the QSO map
QString countryName,continent;
bool callWorkedBefore,countryWorkedBefore,gridWorkedBefore;
m_logBook.match(/*in*/houndCall,"",/*out*/countryName,callWorkedBefore,countryWorkedBefore,
gridWorkedBefore,/*in*/ m_currentBand);
int i1=countryName.lastIndexOf(";");
continent=countryName.mid(i1+2,-1);
auto const& countryName = m_logBook.countries ().find (houndCall);
auto const& continent = countryName.mid (countryName.lastIndexOf (';') + 2, -1);
//If we are using a directed CQ, ignore Hound calls that do not comply.
QString CQtext=ui->comboBoxCQ->currentText();