diff --git a/logbook/adif.cpp b/logbook/adif.cpp new file mode 100644 index 000000000..2a69e3058 --- /dev/null +++ b/logbook/adif.cpp @@ -0,0 +1,112 @@ +#include "adif.h" + +#include +#include + +/* +W1XT20m14.076DM33JT65-21-14201104220417042441st JT65A QSO. Him: mag loop 20WVK3ACFqf22lb +IK1SOW20m14.076JN35JT65-19-1120110422052505333VK3ACFqf22lb +*/ + +void ADIF::init(QString filename) +{ + _filename = filename; + _data.clear(); +} + +QString ADIF::_extractField(const QString line, const QString fieldName) +{ + int s1 = line.indexOf(fieldName,0,Qt::CaseInsensitive); + if (s1 >=0) + { + int s2 = line.indexOf('>',s1); + if (s2 >= 0) + { + int flsi = s1+fieldName.length(); + int flsl = s2-flsi; + if (flsl>0) + { + QString fieldLengthString = line.mid(flsi,flsl); + int fieldLength = fieldLengthString.toInt(); + QString field = line.mid(s2+1,fieldLength); + return field; + } + } + } + return ""; +} + + + +void ADIF::load() +{ + _data.clear(); + QFile inputFile(_filename); + if (inputFile.open(QIODevice::ReadOnly)) + { + QTextStream in(&inputFile); + while ( !in.atEnd() ) + { + QString line = in.readLine(); + QSO q; + q.call = _extractField(line,"CALL:"); + q.band = _extractField(line,"BAND:"); + q.mode = _extractField(line,"MODE:"); + q.date = _extractField(line,"QSO_DATE:"); + if (q.call != "") + _data << q; + } + inputFile.close(); + } +} + + +void ADIF::add(const QString call) +{ + QSO q; + q.call = call; + q.band = ""; //TODO + q.mode = "JT9"; //TODO + q.date = ""; //TODO + _data << q; +} + +// return true if in the log same band and mode (where JT65 == JT9) +bool ADIF::match(const QString call, const QString band, const QString mode) +{ + QSO q; + foreach(q,_data) + { + if (call.compare(q.call) == 0) //TODO handle multiple log entries from same call, should this be a hash table rather than a list? + { + if ((band.compare(q.band) == 0) || (band=="") || (q.band=="")) + { + if ( + ( + ((mode.compare("JT65",Qt::CaseInsensitive)==0) || (mode.compare("JT9",Qt::CaseInsensitive)==0)) + && + ((q.mode.compare("JT65",Qt::CaseInsensitive)==0) || (q.mode.compare("JT9",Qt::CaseInsensitive)==0)) + ) + || (mode.compare(q.mode)==0) + ) + return true; + } + } + } + return false; +} + +QList ADIF::getCallList() +{ + QList p; + QSO q; + foreach(q,_data) + p << q.call; + return p; +} + +int ADIF::getCount() +{ + return _data.length(); +} + diff --git a/logbook/adif.h b/logbook/adif.h new file mode 100644 index 000000000..fe6a53699 --- /dev/null +++ b/logbook/adif.h @@ -0,0 +1,40 @@ +/* + * Reads an ADIF log file into memory + * Searches log for call, band and mode + * VK3ACF July 2013 + */ + + +#ifndef __ADIF_H +#define __ADIF_H + + +#include + + +class ADIF +{ + public: + void init(QString filename); + void load(); + void add(const QString call); + bool match(const QString call, const QString band, const QString mode); + QList getCallList(); + int getCount(); + + + private: + struct QSO + { + QString call,band,mode,date; + }; + + QList _data; + QString _filename; + + QString _extractField(const QString line, const QString fieldName); +}; + + +#endif + diff --git a/logbook/countriesworked.cpp b/logbook/countriesworked.cpp new file mode 100644 index 000000000..a70a4e1af --- /dev/null +++ b/logbook/countriesworked.cpp @@ -0,0 +1,42 @@ +#include "countriesworked.h" + +void CountriesWorked::init(const QStringList countryNames) +{ + _data.clear(); + foreach(QString name,countryNames) + _data.insert(name,false); +} + +void CountriesWorked::setAsWorked(const QString countryName) +{ + if (_data.contains(countryName)) + _data.insert(countryName,true); +} + +bool CountriesWorked::getHasWorked(const QString countryName) +{ + if (_data.contains(countryName)) + return _data.value(countryName); + + return false; +} + +int CountriesWorked::getWorkedCount() +{ + int count = 0; + foreach (bool value,_data) + if (value) + count += 1; + return count; +} + +int CountriesWorked::getSize() +{ + return _data.count(); +} + + + + + + diff --git a/logbook/countriesworked.h b/logbook/countriesworked.h new file mode 100644 index 000000000..87287e2b4 --- /dev/null +++ b/logbook/countriesworked.h @@ -0,0 +1,26 @@ +/* + * maintains a list of country names that have been worked + * VK3ACF July 2013 + */ + +#ifndef __COUNTRIESWORKDED_H +#define __COUNTRIESWORKDED_H + +#include + + +class CountriesWorked +{ + public: + void init(const QStringList countryNames); + void setAsWorked(const QString countryName); + bool getHasWorked(const QString countryName); + int getWorkedCount(); + int getSize(); + + private: + QHash _data; +}; + +#endif + diff --git a/logbook/countrydat.cpp b/logbook/countrydat.cpp new file mode 100644 index 000000000..2cac9b739 --- /dev/null +++ b/logbook/countrydat.cpp @@ -0,0 +1,128 @@ +/* +#Sov Mil Order of Malta: 15: 28: EU: 41.90: -12.43: -1.0: 1A: + #1A; +#Spratly Islands: 26: 50: AS: 9.88: -114.23: -8.0: 1S: + #1S,9M0,BV9S; +#Monaco: 14: 27: EU: 43.73: -7.40: -1.0: 3A: + #3A; +#Heard Island: 39: 68: AF: -53.08: -73.50: -5.0: VK0H: + #=VK0IR; +#Macquarie Island: 30: 60: OC: -54.60: -158.88: -10.0: VK0M: + #=VK0KEV; +#Cocos-Keeling: 29: 54: OC: -12.15: -96.82: -6.5: VK9C: + #AX9C,AX9Y,VH9C,VH9Y,VI9C,VI9Y,VJ9C,VJ9Y,VK9C,VK9Y,VL9C,VL9Y,VM9C,VM9Y, + #VN9C,VN9Y,VZ9C,VZ9Y,=VK9AA; +*/ + + +#include "countrydat.h" +#include +#include + + +void CountryDat::init(const QString filename) +{ + _filename = filename; + _data.clear(); +} + +QString CountryDat::_extractName(const QString line) +{ + int s1 = line.indexOf(':'); + QString name = line.mid(0,s1); + return name; +} + +void CountryDat::_removeBrackets(QString &line, const QString a, const QString b) +{ + int s1 = line.indexOf(a); + while (s1 >= 0) + { + int s2 = line.indexOf(b); + line = line.mid(0,s1) + line.mid(s2+1,-1); + s1 = line.indexOf(a); + } +} + +QStringList CountryDat::_extractPrefix(QString &line, bool &more) +{ + line = line.remove(" \n"); + line = line.replace("=",""); + line = line.replace(" ",""); + + _removeBrackets(line,"(",")"); + _removeBrackets(line,"[","]"); + _removeBrackets(line,"<",">"); + _removeBrackets(line,"~","~"); + + int s1 = line.indexOf(';'); + more = true; + if (s1 >= 0) + { + line = line.mid(0,s1); + more = false; + } + + QStringList r = line.split(','); + + return r; +} + + +void CountryDat::load() +{ + _data.clear(); //dictionary was = {} + _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); + _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 prefix) +{ + while(prefix.length() >= 1) + { + if (_data.contains(prefix)) + { + QString country = _data.value(prefix); + return country; + } + prefix = prefix.left(prefix.length()-1); + } + return ""; +} + + + diff --git a/logbook/countrydat.h b/logbook/countrydat.h new file mode 100644 index 000000000..971b614f6 --- /dev/null +++ b/logbook/countrydat.h @@ -0,0 +1,32 @@ +/* + * Reads cty.dat file + * Establishes a map between prefixes and their country names + * VK3ACF July 2013 + */ + + +#ifndef __COUNTRYDAT_H +#define __COUNTRYDAT_H + +#include + + +class CountryDat +{ + public: + void init(const QString filename); + void load(); + QString find(QString prefix); // return country name or "" + QStringList getCountryNames() { return _countryNames; }; + + private: + QString _extractName(const QString line); + void _removeBrackets(QString &line, const QString a, const QString b); + QStringList _extractPrefix(QString &line, bool &more); + + QString _filename; + QStringList _countryNames; + QHash _data; +}; + +#endif diff --git a/logbook/logbook.cpp b/logbook/logbook.cpp new file mode 100644 index 000000000..90ff8552e --- /dev/null +++ b/logbook/logbook.cpp @@ -0,0 +1,70 @@ +#include "logbook.h" + + +void LogBook::init() +{ + const QString logFilename = "wsjtx_log.adi"; //TODO get from user + const QString countryDataFilename = "cty.dat"; //TODO get from user + + _countries.init(countryDataFilename); + _countries.load(); + + _worked.init(_countries.getCountryNames()); + + _log.init(logFilename); + _log.load(); + + _setAlreadyWorkedFromLog(); + + + int QSOcount = _log.getCount(); + int count = _worked.getWorkedCount(); + qDebug() << QSOcount << "QSOs and" << count << "countries worked in file" << logFilename; +} + + +void LogBook::_setAlreadyWorkedFromLog() +{ + QList calls = _log.getCallList(); + QString c; + foreach(c,calls) + { + QString countryName = _countries.find(c); + if (countryName.length() > 0) + { + _worked.setAsWorked(countryName); + //qDebug() << countryName << " worked " << c; + } + } +} + +void LogBook::match(/*in*/const QString call, + /*out*/ QString &countryName, + bool &callWorkedBefore, + bool &countryWorkedBefore) +{ + if (call.length() > 0) + { + QString currentMode = "JT9"; // JT65 == JT9 in ADIF::match() + QString currentBand = ""; // match any band + callWorkedBefore = _log.match(call,currentBand,currentMode); + countryName = _countries.find(call); + if (countryName.length() > 0) // country was found + countryWorkedBefore = _worked.getHasWorked(countryName); + else + { + countryName = "where?"; //error: prefix not found + countryWorkedBefore = false; + } + } + qDebug() << "Logbook:" << call << ":" << countryName << "Cty B4:" << countryWorkedBefore << "call B4:" << callWorkedBefore; +} + +void LogBook::addAsWorked(const QString call) +{ + qDebug() << "adding " << call << " as worked"; + _log.add(call); + QString countryName = _countries.find(call); + if (countryName.length() > 0) + _worked.setAsWorked(countryName); +} diff --git a/logbook/logbook.h b/logbook/logbook.h new file mode 100644 index 000000000..a4722e192 --- /dev/null +++ b/logbook/logbook.h @@ -0,0 +1,35 @@ +/* + * From an ADIF file and cty.dat, get a call's DXCC entity and its worked before status + * VK3ACF July 2013 + */ + +#ifndef LOGBOOK_H +#define LOGBOOK_H + + +#include + +#include "countrydat.h" +#include "countriesworked.h" +#include "adif.h" + +class LogBook +{ +public: + void init(); + void match(/*in*/ const QString call, + /*out*/ QString &countryName, + bool &callWorkedBefore, + bool &countryWorkedBefore); + void addAsWorked(const QString call); + +private: + CountryDat _countries; + CountriesWorked _worked; + ADIF _log; + + void _setAlreadyWorkedFromLog(); + +}; + +#endif // LOGBOOK_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 4f7538717..531df4907 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -182,6 +182,7 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ m_promptToLog=false; m_blankLine=false; m_insertBlank=false; + m_displayDXCCEntity=false; m_clearCallGrid=false; m_bMiles=false; m_decodedText2=false; @@ -353,6 +354,8 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ psk_Reporter->setLocalStation(m_myCall,m_myGrid, m_antDescription[m_band], "WSJT-X r" + rev.mid(6,4) ); #endif + m_logBook.init(); + ui->label_9->setStyleSheet("QLabel{background-color: #aabec8}"); ui->label_10->setStyleSheet("QLabel{background-color: #aabec8}"); ui->labUTC->setStyleSheet( \ @@ -464,6 +467,7 @@ void MainWindow::writeSettings() settings.setValue("BandIndex",m_band); settings.setValue("PromptToLog",m_promptToLog); settings.setValue("InsertBlank",m_insertBlank); + settings.setValue("DXCCEntity",m_displayDXCCEntity); settings.setValue("ClearCallGrid",m_clearCallGrid); settings.setValue("Miles",m_bMiles); settings.setValue("GUItab",ui->tabWidget->currentIndex()); @@ -607,6 +611,8 @@ void MainWindow::readSettings() ui->actionPrompt_to_log_QSO->setChecked(m_promptToLog); m_insertBlank=settings.value("InsertBlank",false).toBool(); ui->actionBlank_line_between_decoding_periods->setChecked(m_insertBlank); + m_displayDXCCEntity=settings.value("DXCCEntity",false).toBool(); + ui->actionEnable_DXCC_entity->setChecked(m_displayDXCCEntity); m_clearCallGrid=settings.value("ClearCallGrid",false).toBool(); ui->actionClear_DX_Call_and_Grid_after_logging->setChecked(m_clearCallGrid); m_bMiles=settings.value("Miles",false).toBool(); @@ -1473,6 +1479,44 @@ void MainWindow::readFromStdout() //readFromStdout if(m_myCall!="" and t.indexOf(" "+m_myCall+" ")>0) bg="#ff6666"; //red bool bQSO=abs(t.mid(14,4).toInt() - g_pWideGraph->rxFreq()) <= 10; QString t1=t.replace("\n","").mid(0,t.length()-4); + + // if enabled add the DXCC entity and B4 status to the end of the preformated text line t1 + int cqi = t.indexOf(" CQ "); + if (m_displayDXCCEntity && (cqi >= 0)) + { + // extract the CQer's call TODO: does this work with all call formats? What about 'CQ DX'? + int s1 = 4 + t.indexOf(" CQ "); + int s2 = t.indexOf(" ",s1); + QString call = t.mid(s1,s2-s1); + QString countryName; + bool callWorkedBefore; + bool countryWorkedBefore; + m_logBook.match(/*in*/call,/*out*/countryName,callWorkedBefore,countryWorkedBefore); + + t1 = t1.left(36); // reduce trailing white space TODO: hardcoded char count + + if (!countryWorkedBefore) // therefore not worked call either + { + t1 += "!"; + bg = "#66ff66"; // strong green + } + else + if (!callWorkedBefore) // but have worked the country + { + t1 += "~"; + bg = "#76cd76"; // mid green + } + else + { + t1 += " "; // have worked this call before + bg="#9cc79c"; // pale green + } + if (countryName.length()>10) //TODO: hardcoded width. Depends on font and window size/layout + countryName = countryName.left(1)+"."+countryName.right(8); //abreviate the first word to the first letter, show remaining right most chars + t1 += countryName; + } + + QString s = "
" + t1 + "
"; bool b65=t1.indexOf("#")==19; @@ -1489,10 +1533,9 @@ void MainWindow::readFromStdout() //readFromStdout } if(jt9com_.nagain==0) { - if(t.indexOf(" CQ ")>0) bg="#66ff66"; //green if(m_myCall!="" and t.indexOf(" "+m_myCall+" ")>0) bg="#ff6666"; //red QString s = "
" + t1 + "
"; + bg + "\">
" + t1 + "
"; cursor = ui->decodedTextBrowser->textCursor(); cursor.movePosition(QTextCursor::End); bf = cursor.blockFormat(); @@ -2506,6 +2549,7 @@ void MainWindow::acceptQSO2(bool accepted) m_saveComments=logDlg->m_saveComments; m_txPower=logDlg->m_txPower; m_logComments=logDlg->m_comments; + m_logBook.addAsWorked(m_hisCall); if(m_clearCallGrid) { m_hisCall=""; ui->dxCallEntry->setText(""); @@ -2775,6 +2819,13 @@ void MainWindow::on_actionBlank_line_between_decoding_periods_triggered(bool che m_insertBlank=checked; } +void MainWindow::on_actionEnable_DXCC_entity_triggered(bool checked) +{ + m_displayDXCCEntity=checked; + if (checked) + m_logBook.init(); // re-read the log and cty.dat files +} + void MainWindow::on_actionClear_DX_Call_and_Grid_after_logging_triggered(bool checked) { m_clearCallGrid=checked; diff --git a/mainwindow.h b/mainwindow.h index 1efb7de7c..1f1400b9a 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -13,6 +13,7 @@ #include "psk_reporter.h" #include "rigclass.h" #include "signalmeter.h" +#include "logbook/logbook.h" #ifdef WIN32 #include "PSKReporter.h" @@ -134,6 +135,7 @@ private slots: void stopTx2(); void on_actionPrompt_to_log_QSO_triggered(bool checked); void on_actionBlank_line_between_decoding_periods_triggered(bool checked); + void on_actionEnable_DXCC_entity_triggered(bool checked); void on_actionClear_DX_Call_and_Grid_after_logging_triggered(bool checked); void on_actionDisplay_distance_in_miles_triggered(bool checked); void on_pbCallCQ_clicked(); @@ -243,6 +245,7 @@ private: bool m_promptToLog; bool m_blankLine; bool m_insertBlank; + bool m_displayDXCCEntity; bool m_clearCallGrid; bool m_bMiles; bool m_decodedText2; @@ -344,6 +347,8 @@ private: QString *mykey_jt9; PSK_Reporter *psk_Reporter; SignalMeter *signalMeter; + LogBook m_logBook; + //---------------------------------------------------- private functions void readSettings(); diff --git a/mainwindow.ui b/mainwindow.ui index dea517943..91d6f3913 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -1314,7 +1314,7 @@ p, li { white-space: pre-wrap; } - buttonGroup + buttonGroup @@ -1348,7 +1348,7 @@ p, li { white-space: pre-wrap; } true - buttonGroup + buttonGroup @@ -1379,7 +1379,7 @@ p, li { white-space: pre-wrap; } - buttonGroup + buttonGroup @@ -1540,7 +1540,7 @@ p, li { white-space: pre-wrap; } - buttonGroup + buttonGroup @@ -1688,7 +1688,7 @@ p, li { white-space: pre-wrap; } - buttonGroup + buttonGroup @@ -1719,7 +1719,7 @@ p, li { white-space: pre-wrap; } - buttonGroup + buttonGroup @@ -2202,7 +2202,7 @@ p, li { white-space: pre-wrap; } 0 0 760 - 21 + 22 @@ -2245,6 +2245,7 @@ p, li { white-space: pre-wrap; } + @@ -2855,6 +2856,14 @@ p, li { white-space: pre-wrap; } Gray1 + + + true + + + Show DXCC entity and B4 status + + diff --git a/psk_reporter.cpp b/psk_reporter.cpp index 595d5db17..c991de57b 100644 --- a/psk_reporter.cpp +++ b/psk_reporter.cpp @@ -95,10 +95,10 @@ void PSK_Reporter::sendReport() txInfoData_h += "0000"; txInfoData_h.replace("50E3llll", "50E3" + QString("%1").arg(txInfoData_h.length()/2,4,16,QChar('0'))); report_h = header_h + m_rxInfoDescriptor_h + m_txInfoDescriptor_h + rxInfoData_h + txInfoData_h; - qDebug() << "Sending Report TX: "; + //qDebug() << "Sending Report TX: "; } else { report_h = header_h + m_rxInfoDescriptor_h + rxInfoData_h; - qDebug() << "Sending Report RX: "; + //qDebug() << "Sending Report RX: "; } report_h.replace("000Allll", "000A" + QString("%1").arg(report_h.length()/2,4,16,QChar('0'))); diff --git a/wsjtx.pro b/wsjtx.pro index e63148730..45bf7656d 100644 --- a/wsjtx.pro +++ b/wsjtx.pro @@ -16,7 +16,7 @@ VERSION = 1.1 TEMPLATE = app #DEFINES = QT4 DEFINES = QT5 -DEFINES += QAUDIO_INPUT +#DEFINES += QAUDIO_INPUT win32 { DEFINES += WIN32 @@ -41,7 +41,11 @@ SOURCES += main.cpp mainwindow.cpp plotter.cpp about.cpp \ getfile.cpp displaytext.cpp getdev.cpp logqso.cpp \ psk_reporter.cpp rigclass.cpp \ signalmeter.cpp \ - meterwidget.cpp + meterwidget.cpp \ + logbook/logbook.cpp \ + logbook/countrydat.cpp \ + logbook/countriesworked.cpp \ + logbook/adif.cpp win32 { SOURCES += killbyname.cpp @@ -52,7 +56,11 @@ HEADERS += mainwindow.h plotter.h soundin.h soundout.h \ commons.h sleep.h displaytext.h logqso.h \ psk_reporter.h rigclass.h \ signalmeter.h \ - meterwidget.h + meterwidget.h \ + logbook/logbook.h \ + logbook/countrydat.h \ + logbook/countriesworked.h \ + logbook/adif.h # (Is the following really needed???) #DEFINES += __cplusplus