Proper handling of directed messages for compound calls using an alias scheme

This commit is contained in:
Jordan Sherer 2018-07-24 15:19:02 -04:00
parent 706a9b1ebd
commit 0bf2afa5f8
3 changed files with 145 additions and 38 deletions

View File

@ -90,7 +90,7 @@ bool DecodedText::tryUnpackCompound(){
}
compound_ = QString("%1/%2").arg(parts.at(0), parts.at(1));
message_ = QString("%1:").arg(compound_);
message_ = QString("%1: ").arg(compound_);
return true;
}
@ -110,10 +110,10 @@ bool DecodedText::tryUnpackDirected(){
if(parts.length() == 3){
// replace it with the correct unpacked (directed)
message_ = QString("%1: %2%3").arg(parts.at(0), parts.at(1), parts.at(2));
message_ = QString("%1: %2%3 ").arg(parts.at(0), parts.at(1), parts.at(2));
} else if(parts.length() == 4){
// replace it with the correct unpacked (directed numeric)
message_ = QString("%1: %2%3 %4").arg(parts.at(0), parts.at(1), parts.at(2), parts.at(3));
message_ = QString("%1: %2%3 %4 ").arg(parts.at(0), parts.at(1), parts.at(2), parts.at(3));
} else {
// replace it with the correct unpacked (freetext)
message_ = QString(parts.join(""));

View File

@ -164,7 +164,7 @@ namespace
{
Radio::Frequency constexpr default_frequency {14074000};
QRegExp message_alphabet {"[- A-Za-z0-9+./?:!^]*"};
QRegExp message_input_alphabet {"[- A-Za-z0-9+./?\\n:!^@&|]*"}; // @&| are used for commands but are never transmitted
QRegExp message_input_alphabet {"[- A-Za-z0-9+./?\\n:!^@&|$]*"}; // @&|$ are used for commands but are never transmitted
// grid exact match excluding RR73
QRegularExpression grid_regexp {"\\A(?![Rr]{2}73)[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}\\z"};
@ -1027,7 +1027,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
ui->dxCallEntry->clear();
ui->dxGridEntry->clear();
auto f = findFreeFreqOffset(500, 2000, 50);
setFreq4(f, f);
setFreqForRestore(f, false);
ui->cbVHFcontest->setChecked(false); // this needs to always be false
ui->spotButton->setChecked(m_config.spot_to_psk_reporter());
@ -1043,21 +1043,18 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect(clearAction1, &QAction::triggered, this, [this](){ this->on_clearAction_triggered(ui->textEditRX); });
ui->textEditRX->setContextMenuPolicy(Qt::ActionsContextMenu);
ui->textEditRX->addAction(clearAction1);
ui->textEditRX->addAction(clearActionSep);
ui->textEditRX->addAction(clearActionAll);
auto clearAction2 = new QAction(QIcon::fromTheme("edit-clear"), QString("Clear"), ui->extFreeTextMsgEdit);
connect(clearAction2, &QAction::triggered, this, [this](){ this->on_clearAction_triggered(ui->extFreeTextMsgEdit); });
ui->extFreeTextMsgEdit->setContextMenuPolicy(Qt::ActionsContextMenu);
ui->extFreeTextMsgEdit->addAction(clearAction2);
ui->extFreeTextMsgEdit->addAction(clearActionSep);
ui->extFreeTextMsgEdit->addAction(clearActionAll);
auto clearAction3 = new QAction(QIcon::fromTheme("edit-clear"), QString("Clear"), ui->tableWidgetRXAll);
connect(clearAction3, &QAction::triggered, this, [this](){ this->on_clearAction_triggered(ui->tableWidgetRXAll); });
ui->tableWidgetRXAll->setContextMenuPolicy(Qt::ActionsContextMenu);
ui->tableWidgetRXAll->addAction(clearAction3);
ui->tableWidgetRXAll->addAction(clearActionSep);
ui->tableWidgetRXAll->addAction(clearActionAll);
auto clearAction4 = new QAction(QIcon::fromTheme("edit-clear"), QString("Clear"), ui->tableWidgetCalls);
@ -1073,7 +1070,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
menu->addSeparator();
menu->addAction(clearAction4);
menu->addSeparator();
menu->addAction(clearActionAll);
menu->popup(ui->tableWidgetCalls->mapToGlobal(point));
@ -3231,6 +3227,7 @@ void MainWindow::readFromStdout() //readFromStdout
ActivityDetail d;
d.isLowConfidence = decodedtext.isLowConfidence();
d.isFree = !decodedtext.isStandardMessage();
d.isCompound = decodedtext.isCompoundMessage();
d.bits = decodedtext.bits();
d.firstCall = decodedtext.CQersCall();
if(d.firstCall.isEmpty()){
@ -3245,7 +3242,7 @@ void MainWindow::readFromStdout() //readFromStdout
}
d.freq = offset;
d.text = decodedtext.messageWords().first().trimmed();
d.text = decodedtext.messageWords().isEmpty() ? "" : decodedtext.messageWords().first().trimmed();
d.utcTimestamp = QDateTime::currentDateTimeUtc();
d.snr = decodedtext.snr();
m_bandActivity[offset].append(d);
@ -3333,6 +3330,24 @@ void MainWindow::readFromStdout() //readFromStdout
cd.utcTimestamp = d.utcTimestamp;
m_callActivity[Radio::base_callsign(cd.call)] = cd;
bool shouldCaptureThirdPartyCallsigns = false;
// check to see if this is a station we've heard 3rd party
if(shouldCaptureThirdPartyCallsigns && Radio::base_callsign(d.to) != Radio::base_callsign(m_config.my_callsign())){
QString relayCall = QString("%1|%2").arg(Radio::base_callsign(d.from)).arg(Radio::base_callsign(d.to));
int snr = -100;
if(parts.length() == 4){
snr = QString(parts.at(3)).toInt();
}
CallDetail td;
td.through = d.from;
td.call = d.to;
td.grid = "";
td.snr = snr;
td.freq = d.freq;
td.utcTimestamp = d.utcTimestamp;
m_callActivity[relayCall] = td;
}
int nsec=QDateTime::currentMSecsSinceEpoch()/1000-m_secBandChanged;
bool okToPost=(nsec>(4*m_TRperiod)/5);
if (okToPost){
@ -3412,10 +3427,10 @@ void MainWindow::readFromStdout() //readFromStdout
// TODO: jsherer - parse decode...
RXDetail d;
d.isFree = !decodedtext.isStandardMessage();
d.isCompound = decodedtext.isCompoundMessage();
d.bits = decodedtext.bits();
d.freq = audioFreq;
d.text = decodedtext.message();
qDebug() << d.text;
d.utcTimestamp = QDateTime::currentDateTimeUtc();
m_rxFrameQueue.append(d);
}
@ -5369,7 +5384,9 @@ void MainWindow::clearActivity(){
// this is now duplicated in three places :(
ui->tableWidgetCalls->insertRow(ui->tableWidgetCalls->rowCount());
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, new QTableWidgetItem("ALLCALL"));
auto item = new QTableWidgetItem("ALLCALL");
item->setData(Qt::UserRole, QVariant("ALLCALL"));
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, item);
ui->tableWidgetCalls->setSpan(ui->tableWidgetCalls->rowCount() - 1, 0, 1, ui->tableWidgetCalls->columnCount());
clearTableWidget(ui->tableWidgetRXAll);
@ -5380,9 +5397,17 @@ void MainWindow::clearActivity(){
ui->extFreeTextMsgEdit->clear();
}
int MainWindow::logRxTxMessageText(QDateTime date, bool isFree, QString text, int freq, bool tx, int block){
int MainWindow::logRxTxMessageText(QDateTime date, QString text, int freq, bool tx, int block){
auto c = ui->textEditRX->textCursor();
// fixup compound callsigns cache / aliases...
foreach(auto call, m_compoundCallCache.keys()){
//QRegExp re(QString("").arg(call));
//text = text.replace(call, m_compoundCallCache[call]);
QRegularExpression re(QString(R"((?<![\/])%1(?![\/]))").arg(QRegularExpression::escape(call)));
text = text.replace(re, m_compoundCallCache[call]);
}
bool found = false;
if(block != -1){
QTextBlock b = c.document()->findBlockByNumber(block);
@ -5470,7 +5495,7 @@ void MainWindow::createMessageTransmitQueue(QString const& text){
m_txFrameCount = frames.length();
int freq = currentFreq();
logRxTxMessageText(QDateTime::currentDateTimeUtc(), false, lines.join(""), freq, true);
logRxTxMessageText(QDateTime::currentDateTimeUtc(), lines.join(""), freq, true);
}
void MainWindow::resetMessageTransmitQueue(){
@ -5557,6 +5582,7 @@ QPair<QStringList, QStringList> MainWindow::buildFT8MessageFrames(QString const&
QStringList lines;
// prepare compound
bool compoundSent = false;
bool compound = Radio::is_compound_callsign(m_config.my_callsign());
QString mycall = m_config.my_callsign();
QString basecall = Radio::base_callsign(m_config.my_callsign());
@ -5625,11 +5651,12 @@ QPair<QStringList, QStringList> MainWindow::buildFT8MessageFrames(QString const&
}
if(useDir){
if(compound){
if(compound && !compoundSent){
QString compoundFrame = Varicode::packCompoundMessage(basecall, fix, prefix, 0);
if(!compoundFrame.isEmpty()){
frames.append(compoundFrame);
lines.append(QString("%1: ").arg(mycall));
compoundSent = true;
}
}
@ -5637,10 +5664,10 @@ QPair<QStringList, QStringList> MainWindow::buildFT8MessageFrames(QString const&
// TODO: jsherer - would be nice to clean this up and have an object that can just decode the actual transmitted frames instead.
if(!line.startsWith(basecall) && !compound){
lines.append(QString("%1: ").arg(basecall));
lines.append(QString("%1: ").arg(lookupCallInCompoundCache(basecall)));
}
lines.append(line.left(n));
lines.append(line.left(n) + " ");
line = line.mid(n);
}
@ -5924,7 +5951,11 @@ QString MainWindow::calculateDistance(QString const& grid)
// this function is called by auto_tx_mode, which is called by autoButton.clicked
void MainWindow::on_startTxButton_toggled(bool checked)
{
if(checked){
toggleTx(checked);
}
void MainWindow::toggleTx(bool start){
if(start){
createMessage(ui->extFreeTextMsgEdit->toPlainText());
startTx();
} else {
@ -6963,8 +6994,10 @@ void MainWindow::on_clearAction_triggered(QObject * sender){
m_callActivity.clear();
clearTableWidget((ui->tableWidgetCalls));
auto item = new QTableWidgetItem("ALLCALL");
item->setData(Qt::UserRole, QVariant("ALLCALL"));
ui->tableWidgetCalls->insertRow(ui->tableWidgetCalls->rowCount());
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, new QTableWidgetItem("ALLCALL"));
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, item);
ui->tableWidgetCalls->setSpan(ui->tableWidgetCalls->rowCount() - 1, 0, 1, ui->tableWidgetCalls->columnCount());
}
@ -7018,7 +7051,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
bool isAllCall = call == "ALLCALL";
auto sendReplyAction = menu->addAction("Reply to Callsign");
auto sendReplyAction = menu->addAction("CALL - Send a message to selected callsign");
connect(sendReplyAction, &QAction::triggered, this, [this](){
@ -7031,7 +7064,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
});
auto sendSNRAction = menu->addAction("Send Signal Report");
auto sendSNRAction = menu->addAction("SNR - Send a signal report to the selected callsign");
sendSNRAction->setEnabled(m_callActivity.contains(call));
connect(sendSNRAction, &QAction::triggered, this, [this](){
@ -7046,6 +7079,9 @@ void MainWindow::buildQueryMenu(QMenu * menu){
auto d = m_callActivity[selectedCall];
addMessageText(QString("%1 SNR %2").arg(selectedCall).arg(Varicode::formatSNR(d.snr)), true);
// perhaps a better name here?
toggleTx(true);
});
menu->addSeparator();
@ -7060,6 +7096,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
}
addMessageText(QString("%1?").arg(selectedCall), true);
toggleTx(true);
});
auto qthAction = menu->addAction("@ - What is your QTH message?");
@ -7072,6 +7109,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
}
addMessageText(QString("%1@").arg(selectedCall), true);
toggleTx(true);
});
auto stationAction = menu->addAction("&& - What is your station message?");
@ -7084,9 +7122,22 @@ void MainWindow::buildQueryMenu(QMenu * menu){
}
addMessageText(QString("%1&").arg(selectedCall), true);
toggleTx(true);
});
auto heardAction = menu->addAction("$ - What stations are you hearing?");
heardAction->setDisabled(isAllCall);
connect(heardAction, &QAction::triggered, this, [this](){
QString selectedCall = callsignSelected();
if(selectedCall.isEmpty()){
return;
}
addMessageText(QString("%1$").arg(selectedCall), true);
toggleTx(true);
});
//menu->addAction("$ - What stations are you hearing?")->setEnabled(false);
//menu->addAction("| - Please relay the following message")->setEnabled(false);
menu->addSeparator();
@ -7100,6 +7151,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
}
addMessageText(QString("%1 AGN?").arg(selectedCall), true);
toggleTx(true);
});
auto rrAction = menu->addAction("RR - I acknowledge your last transmission");
@ -7111,6 +7163,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
}
addMessageText(QString("%1 RR").arg(selectedCall), true);
toggleTx(true);
});
@ -7123,6 +7176,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
}
addMessageText(QString("%1 YES").arg(selectedCall), true);
toggleTx(true);
});
auto noAction = menu->addAction("NO - I do not confirm your last inquiry");
@ -7134,6 +7188,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
}
addMessageText(QString("%1 NO").arg(selectedCall), true);
toggleTx(true);
});
auto sevenThreeAction = menu->addAction("73 - I send my best regards / end of contact");
@ -7145,6 +7200,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){
}
addMessageText(QString("%1 73").arg(selectedCall), true);
toggleTx(true);
});
}
@ -7192,7 +7248,7 @@ void MainWindow::on_tableWidgetRXAll_cellClicked(int row, int col){
auto item = ui->tableWidgetRXAll->item(row, 0);
int offset = item->text().toInt();
setFreq4(offset, offset);
setFreqForRestore(offset, false);
ui->tableWidgetCalls->selectionModel()->select(
ui->tableWidgetCalls->selectionModel()->selection(),
@ -7215,13 +7271,17 @@ void MainWindow::on_tableWidgetRXAll_cellDoubleClicked(int row, int col){
if(activityAging && d.utcTimestamp.secsTo(now)/60 >= activityAging){
continue;
}
// TODO: jsherer - still ok to skip these here?
if(d.isCompound){
continue;
}
if(activityText.isEmpty()){
firstActivity = d.utcTimestamp;
}
activityText.append(d.text);
}
if(!activityText.isEmpty()){
int block = logRxTxMessageText(firstActivity, true, activityText, offset, false);
int block = logRxTxMessageText(firstActivity, activityText, offset, false);
m_rxFrameBlockNumbers[offset] = block;
m_rxRecentCache.insert(offset/10*10, new QDateTime(QDateTime::currentDateTimeUtc()), 25);
}
@ -7242,14 +7302,20 @@ void MainWindow::on_tableWidgetRXAll_selectionChanged(const QItemSelection &sele
}
void MainWindow::on_tableWidgetCalls_cellClicked(int row, int col){
/*
auto item = ui->tableWidgetCalls->item(row, 0);
auto call = Radio::base_callsign(item->text());
auto call = Radio::base_callsign(item->data(Qt::UserRole).toString());
//auto call = Radio::base_callsign(item->text());
*/
auto call = callsignSelected();
if(!m_callActivity.contains(call)){
return;
}
auto d = m_callActivity[call];
setFreqForRestore(d.freq, true);
setFreqForRestore(d.freq, false);
ui->tableWidgetRXAll->selectionModel()->select(
ui->tableWidgetRXAll->selectionModel()->selection(),
@ -7259,8 +7325,13 @@ void MainWindow::on_tableWidgetCalls_cellClicked(int row, int col){
void MainWindow::on_tableWidgetCalls_cellDoubleClicked(int row, int col){
on_tableWidgetCalls_cellClicked(row, col);
/*
auto item = ui->tableWidgetCalls->item(row, 0);
auto call = Radio::base_callsign(item->text());
auto call = Radio::base_callsign(item->data(Qt::UserRole).toString());
//auto call = Radio::base_callsign(item->text());
*/
auto call = callsignSelected();
addMessageText(call);
}
@ -8188,7 +8259,10 @@ void MainWindow::updateButtonDisplay(){
QString MainWindow::callsignSelected(){
if(!ui->tableWidgetCalls->selectedItems().isEmpty()){
auto selectedCalls = ui->tableWidgetCalls->selectedItems();
return Radio::base_callsign(selectedCalls.first()->text());
if(!selectedCalls.isEmpty()){
auto call = selectedCalls.first()->data(Qt::UserRole).toString();
return Radio::base_callsign(call);
}
}
if(!ui->tableWidgetRXAll->selectedItems().isEmpty()){
@ -8311,6 +8385,7 @@ void MainWindow::displayActivity(bool force){
ui->tableWidgetRXAll->insertRow(ui->tableWidgetRXAll->rowCount());
auto offsetItem = new QTableWidgetItem(QString("%1").arg(offset));
offsetItem->setData(Qt::UserRole, QVariant(offset));
ui->tableWidgetRXAll->setItem(ui->tableWidgetRXAll->rowCount() - 1, 0, offsetItem);
auto ageItem = new QTableWidgetItem(QString("(%1)").arg(age));
@ -8367,7 +8442,9 @@ void MainWindow::displayActivity(bool force){
clearTableWidget(ui->tableWidgetCalls);
ui->tableWidgetCalls->insertRow(ui->tableWidgetCalls->rowCount());
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, new QTableWidgetItem("ALLCALL"));
auto item = new QTableWidgetItem("ALLCALL");
item->setData(Qt::UserRole, QVariant("ALLCALL"));
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, item);
ui->tableWidgetCalls->setSpan(ui->tableWidgetCalls->rowCount() - 1, 0, 1, ui->tableWidgetCalls->columnCount());
if(selectedCall == "ALLCALL"){
ui->tableWidgetCalls->selectRow(ui->tableWidgetCalls->rowCount() - 1);
@ -8384,7 +8461,11 @@ void MainWindow::displayActivity(bool force){
}
ui->tableWidgetCalls->insertRow(ui->tableWidgetCalls->rowCount());
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, new QTableWidgetItem(d.call));
QString displayCall = d.through.isEmpty() ? d.call : QString("%1 | %2").arg(d.through).arg(d.call);
auto displayItem = new QTableWidgetItem(displayCall);
displayItem->setData(Qt::UserRole, QVariant((d.call)));
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, displayItem);
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 1, new QTableWidgetItem(QString("(%1)").arg(since(d.utcTimestamp))));
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 2, new QTableWidgetItem(QString("%1").arg(Varicode::formatSNR(d.snr))));
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 3, new QTableWidgetItem(QString("%1").arg(d.grid)));
@ -8406,8 +8487,12 @@ void MainWindow::displayActivity(bool force){
// Recently Directed Activity
while(!m_rxFrameQueue.isEmpty()){
RXDetail d = m_rxFrameQueue.first();
m_rxFrameQueue.removeFirst();
RXDetail d = m_rxFrameQueue.dequeue();
// TODO: jsherer - is it safe to just ignore printing these?
if(d.isCompound){
continue;
}
bool isLast = d.bits == Varicode::FT8CallLast;
@ -8417,7 +8502,7 @@ void MainWindow::displayActivity(bool force){
int freq = d.freq/10*10;
int block = m_rxFrameBlockNumbers.contains(freq) ? m_rxFrameBlockNumbers[freq] : -1;
block = logRxTxMessageText(d.utcTimestamp, d.isFree, d.text, d.freq, false, block);
block = logRxTxMessageText(d.utcTimestamp, d.text, d.freq, false, block);
m_rxFrameBlockNumbers[freq] = block;
if(isLast){
@ -8447,7 +8532,7 @@ void MainWindow::displayActivity(bool force){
continue;
}
// we're only processing allcall and our callsign at this point
// we're only responding to allcall and our callsign at this point, but we'll log callsigns we've heard
if(!isAllCall && d.to != m_config.my_callsign().trimmed() && d.to != Radio::base_callsign(m_config.my_callsign()).trimmed()){
continue;
}
@ -8460,11 +8545,11 @@ void MainWindow::displayActivity(bool force){
// construct reply
QString reply;
// SNR
// QUERIED SNR
if(d.cmd == "?"){
reply = QString("%1 SNR %2").arg(Radio::base_callsign(d.from)).arg(Varicode::formatSNR(d.snr));
}
// QTH
// QUERIED QTH
else if(d.cmd == "@" && !isAllCall){
QString qth = m_config.my_qth();
if(qth.isEmpty()){
@ -8477,9 +8562,27 @@ void MainWindow::displayActivity(bool force){
reply = QString("%1 %2").arg(Radio::base_callsign(d.from)).arg(qth);
}
// STATION MESSAGE
// QUERIED STATION MESSAGE
else if(d.cmd == "&" && !isAllCall){
reply = QString("%1 %2").arg(Radio::base_callsign(d.from)).arg(m_config.my_station());
}
// QUERIED STATIONS HEARD
else if(d.cmd == "$" && !isAllCall){
auto calls = m_callActivity.keys();
qSort(calls.begin(), calls.end(), [this](QString const &a, QString const &b){
auto left = m_callActivity[a];
auto right = m_callActivity[b];
return right.snr < left.snr;
});
QStringList lines;
foreach(auto call, calls){
auto d = m_callActivity[call];
lines.append(QString("%1 SNR %2").arg(Radio::base_callsign(call)).arg(Varicode::formatSNR(d.snr)));
}
reply = lines.join('\n');
} else {
continue;
}

View File

@ -123,7 +123,7 @@ public slots:
QString lookupCallInCompoundCache(QString const &call);
void clearActivity();
int logRxTxMessageText(QDateTime date, bool isFree, QString text, int freq, bool tx, int block=-1);
int logRxTxMessageText(QDateTime date, QString text, int freq, bool tx, int block=-1);
void addMessageText(QString text, bool clear=false);
void resetMessage();
void resetMessageUI();
@ -199,6 +199,7 @@ private slots:
void on_txb5_doubleClicked ();
void on_txb6_clicked();
void on_startTxButton_toggled(bool checked);
void toggleTx(bool start);
void splitAndSendNextMessage();
void on_rbNextFreeTextMsg_toggled (bool status);
void on_lookupButton_clicked();
@ -639,6 +640,7 @@ private:
struct CallDetail
{
QString call;
QString through;
QString grid;
int freq;
QDateTime utcTimestamp;
@ -659,6 +661,7 @@ private:
{
bool isFree;
bool isLowConfidence;
bool isCompound;
int bits;
QString firstCall;
QString secondCall;
@ -672,6 +675,7 @@ private:
{
bool isFree;
bool isLowConfidence;
bool isCompound;
int bits;
int freq;
QString text;