mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
343 lines
10 KiB
C++
343 lines
10 KiB
C++
/**
|
|
* @file LogQSO.cpp
|
|
* @brief implementation of the LogQSO dialog for the UI
|
|
*/
|
|
|
|
#include "LogQSO.h"
|
|
#include "Configuration.h"
|
|
#include "JS8_Include/Maidenhead.h"
|
|
#include "JS8_Logbook/ADIF.h"
|
|
#include "JS8_Main/Bands.h"
|
|
#include "JS8_Main/DriftingDateTime.h"
|
|
#include "JS8_Main/JS8MessageBox.h"
|
|
#include "ui_LogQSO.h"
|
|
|
|
#include <QAbstractItemView>
|
|
#include <QComboBox>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QLoggingCategory>
|
|
#include <QSettings>
|
|
#include <QStandardPaths>
|
|
#include <QString>
|
|
#include <QUdpSocket>
|
|
|
|
#include "moc_LogQSO.cpp"
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(logqso_js8)
|
|
|
|
LogQSO::LogQSO(QString const &programTitle, QSettings *settings,
|
|
Configuration const *config, QWidget *parent)
|
|
: QDialog{parent, Qt::WindowStaysOnTopHint | Qt::WindowTitleHint |
|
|
Qt::WindowSystemMenuHint},
|
|
ui(new Ui::LogQSO), m_settings(settings), m_config{config} {
|
|
ui->setupUi(this);
|
|
setWindowTitle(programTitle + " - Log QSO");
|
|
ui->grid->setValidator(new Maidenhead::StandardValidator{this});
|
|
|
|
auto b = ui->buttonBox->button(QDialogButtonBox::Save);
|
|
if (b) {
|
|
b->setText("Add to Log");
|
|
}
|
|
}
|
|
|
|
LogQSO::~LogQSO() {}
|
|
|
|
bool LogQSO::acceptText(QString text) {
|
|
auto w = focusWidget();
|
|
if (!w) {
|
|
return false;
|
|
}
|
|
|
|
auto name = QString(w->metaObject()->className());
|
|
if (name != "QLineEdit") {
|
|
return false;
|
|
}
|
|
|
|
auto l = static_cast<QLineEdit *>(w);
|
|
if (l->text().isEmpty()) {
|
|
// set
|
|
l->setText(text);
|
|
} else {
|
|
// append
|
|
l->setText(QString("%1 %2").arg(l->text()).arg(text));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QString LogQSO::currentCall() { return ui->call->text().trimmed(); }
|
|
|
|
void LogQSO::on_start_now_button_pressed() {
|
|
ui->start_date_time->setDateTime(DriftingDateTime::currentDateTimeUtc());
|
|
}
|
|
|
|
void LogQSO::on_end_now_button_pressed() {
|
|
ui->end_date_time->setDateTime(DriftingDateTime::currentDateTimeUtc());
|
|
}
|
|
|
|
void LogQSO::on_add_new_field_button_pressed() { createAdditionalField(); }
|
|
|
|
void LogQSO::createAdditionalField(QString key, QString value) {
|
|
QLineEdit *l = new QLineEdit(this);
|
|
if (!value.isEmpty()) {
|
|
l->setText(value);
|
|
}
|
|
|
|
QComboBox *c = new QComboBox(this);
|
|
c->insertItems(0, ADIF_FIELDS);
|
|
c->insertItem(0, "");
|
|
c->setEditable(true);
|
|
c->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
connect(c, &QComboBox::currentTextChanged, this, [l](const QString &text) {
|
|
l->setProperty("fieldKey", QVariant(text));
|
|
});
|
|
c->setCurrentText(key);
|
|
|
|
auto layout = static_cast<QFormLayout *>(ui->additionalFields->layout());
|
|
layout->addRow(c, l);
|
|
|
|
// set tab ordering
|
|
if (m_additionalFieldsControls.isEmpty()) {
|
|
setTabOrder(ui->cbComments, c);
|
|
} else {
|
|
setTabOrder(m_additionalFieldsControls.last(), c);
|
|
}
|
|
setTabOrder(c, l);
|
|
setTabOrder(l, ui->add_new_field_button);
|
|
c->setFocus();
|
|
|
|
m_additionalFieldsControls.append(l);
|
|
ui->additionalFields->setVisible(true);
|
|
ui->additionalFields->adjustSize();
|
|
|
|
// update the window layout
|
|
updateGeometry();
|
|
}
|
|
|
|
QVariantMap LogQSO::collectAdditionalFields() {
|
|
QVariantMap additionalFields;
|
|
foreach (auto field, m_additionalFieldsControls) {
|
|
auto key = field->property("fieldKey").toString();
|
|
if (key.isEmpty()) {
|
|
continue;
|
|
}
|
|
additionalFields[key] = QVariant(field->text());
|
|
}
|
|
return additionalFields;
|
|
}
|
|
|
|
void LogQSO::resetAdditionalFields() {
|
|
ui->additionalFields->setVisible(false);
|
|
|
|
if (!m_additionalFieldsControls.isEmpty()) {
|
|
auto layout =
|
|
static_cast<QFormLayout *>(ui->additionalFields->layout());
|
|
|
|
#if QT_VERSION >= 0x050800
|
|
for (int i = 0, count = layout->rowCount(); i < count; i++) {
|
|
layout->removeRow(0);
|
|
}
|
|
#else
|
|
QLayoutItem *child;
|
|
while ((child = layout->takeAt(0)) != 0) {
|
|
delete child;
|
|
}
|
|
#endif
|
|
|
|
m_additionalFieldsControls.clear();
|
|
}
|
|
|
|
setTabOrder(ui->cbComments, ui->add_new_field_button);
|
|
updateGeometry();
|
|
}
|
|
|
|
void LogQSO::loadSettings() {
|
|
m_settings->beginGroup("LogQSO");
|
|
restoreGeometry(
|
|
m_settings->value("geometry", saveGeometry()).toByteArray());
|
|
ui->cbComments->setChecked(
|
|
m_settings->value("SaveComments", false).toBool());
|
|
m_comments = m_settings->value("LogComments", "").toString();
|
|
|
|
resetAdditionalFields();
|
|
auto additionalFields =
|
|
m_settings->value("AdditionalFields", {}).toStringList();
|
|
QSet<QString> additionalFieldsSet;
|
|
foreach (auto key, additionalFields) {
|
|
if (key.isEmpty()) {
|
|
continue;
|
|
}
|
|
if (additionalFieldsSet.contains(key)) {
|
|
continue;
|
|
}
|
|
|
|
createAdditionalField(key);
|
|
additionalFieldsSet.insert(key);
|
|
}
|
|
|
|
m_settings->endGroup();
|
|
}
|
|
|
|
void LogQSO::storeSettings() const {
|
|
m_settings->beginGroup("LogQSO");
|
|
m_settings->setValue("geometry", saveGeometry());
|
|
m_settings->setValue("SaveComments", ui->cbComments->isChecked());
|
|
m_settings->setValue("LogComments", m_comments);
|
|
|
|
auto additionalFields = QStringList{};
|
|
foreach (auto field, m_additionalFieldsControls) {
|
|
auto key = field->property("fieldKey").toString();
|
|
if (key.isEmpty()) {
|
|
continue;
|
|
}
|
|
additionalFields.append(key);
|
|
}
|
|
m_settings->setValue("AdditionalFields", additionalFields);
|
|
|
|
m_settings->endGroup();
|
|
}
|
|
|
|
void LogQSO::initLogQSO(QString const &hisCall, QString const &hisGrid,
|
|
QString mode, QString const &rptSent,
|
|
QString const &rptRcvd, QDateTime const &dateTimeOn,
|
|
QDateTime const &dateTimeOff, Radio::Frequency dialFreq,
|
|
QString const &myCall, QString const &myGrid,
|
|
QString const &opCall, QString const &comments) {
|
|
if (!isHidden())
|
|
return;
|
|
|
|
loadSettings();
|
|
|
|
ui->call->setFocus();
|
|
ui->call->setText(hisCall);
|
|
ui->grid->setText(hisGrid);
|
|
ui->name->setText("");
|
|
ui->comments->setText("");
|
|
|
|
if (ui->cbComments->isChecked())
|
|
ui->comments->setText(m_comments);
|
|
|
|
if (!comments.isEmpty()) {
|
|
ui->comments->setText(comments);
|
|
}
|
|
|
|
ui->mode->setText(mode);
|
|
ui->sent->setText(rptSent);
|
|
ui->rcvd->setText(rptRcvd);
|
|
ui->start_date_time->setDateTime(dateTimeOn);
|
|
ui->end_date_time->setDateTime(dateTimeOff);
|
|
|
|
m_dialFreq = dialFreq;
|
|
m_myCall = myCall;
|
|
m_myGrid = myGrid;
|
|
|
|
ui->band->setText(m_config->bands()->find(dialFreq));
|
|
ui->loggedOperator->setText(opCall);
|
|
|
|
qCDebug(logqso_js8) << "initLogQSO rptSent (" << rptSent << ")";
|
|
qCDebug(logqso_js8) << "initLogQSO rptRcvd (" << rptRcvd << ")";
|
|
|
|
show();
|
|
}
|
|
|
|
void LogQSO::accept() {
|
|
QString hisCall, hisGrid, mode, submode, rptSent, rptRcvd, dateOn, dateOff,
|
|
timeOn, timeOff, band, operator_call;
|
|
QString comments, name;
|
|
|
|
hisCall = ui->call->text().toUpper();
|
|
hisGrid = ui->grid->text().toUpper();
|
|
mode = ui->mode->text().toUpper();
|
|
if (mode == "JS8") {
|
|
mode = "MFSK";
|
|
submode = "JS8";
|
|
}
|
|
rptSent = ui->sent->text();
|
|
rptRcvd = ui->rcvd->text();
|
|
m_dateTimeOn = ui->start_date_time->dateTime();
|
|
m_dateTimeOff = ui->end_date_time->dateTime();
|
|
band = ui->band->text();
|
|
name = ui->name->text();
|
|
comments = ui->comments->text();
|
|
m_comments = comments;
|
|
QString strDialFreq(QString::number(m_dialFreq / 1.e6, 'f', 6));
|
|
operator_call = ui->loggedOperator->text();
|
|
|
|
// Log this QSO to ADIF file "js8call_log.adi"
|
|
QString filename = "js8call_log.adi"; // TODO allow user to set
|
|
ADIF adifile;
|
|
auto adifilePath = QDir{QStandardPaths::writableLocation(
|
|
QStandardPaths::AppLocalDataLocation)}
|
|
.absoluteFilePath(filename);
|
|
adifile.init(adifilePath);
|
|
|
|
auto additionalFields = collectAdditionalFields();
|
|
|
|
QByteArray ADIF{adifile.QSOToADIF(
|
|
hisCall, hisGrid, mode, submode, rptSent, rptRcvd, m_dateTimeOn,
|
|
m_dateTimeOff, band, comments, name, strDialFreq, m_myCall, m_myGrid,
|
|
operator_call, additionalFields)};
|
|
|
|
if (!adifile.addQSOToFile(ADIF)) {
|
|
JS8MessageBox::warning_message(
|
|
this, tr("Log file error"),
|
|
tr("Cannot open \"%1\"").arg(adifilePath));
|
|
}
|
|
|
|
// Log this QSO to file "js8call.log"
|
|
static QFile f{QDir{
|
|
QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)}
|
|
.absoluteFilePath("js8call.log")};
|
|
if (!f.open(QIODevice::Text | QIODevice::Append)) {
|
|
JS8MessageBox::warning_message(
|
|
this, tr("Log file error"),
|
|
tr("Cannot open \"%1\" for append").arg(f.fileName()),
|
|
tr("Error: %1").arg(f.errorString()));
|
|
} else {
|
|
QStringList logEntryItems = {
|
|
m_dateTimeOn.date().toString("yyyy-MM-dd"),
|
|
m_dateTimeOn.time().toString("hh:mm:ss"),
|
|
m_dateTimeOff.date().toString("yyyy-MM-dd"),
|
|
m_dateTimeOff.time().toString("hh:mm:ss"),
|
|
hisCall,
|
|
hisGrid,
|
|
strDialFreq,
|
|
(mode == "MFSK" ? "JS8" : mode),
|
|
rptSent,
|
|
rptRcvd,
|
|
comments,
|
|
name};
|
|
|
|
if (!additionalFields.isEmpty()) {
|
|
foreach (auto value, additionalFields.values()) {
|
|
logEntryItems.append(value.toString());
|
|
}
|
|
}
|
|
|
|
QTextStream out(&f);
|
|
out << logEntryItems.join(",") << Qt::endl;
|
|
out.flush();
|
|
flushFileBuffer(f);
|
|
f.close();
|
|
}
|
|
|
|
Q_EMIT acceptQSO(m_dateTimeOff, hisCall, hisGrid, m_dialFreq, mode, submode,
|
|
rptSent, rptRcvd, comments, name, m_dateTimeOn,
|
|
operator_call, m_myCall, m_myGrid, ADIF, additionalFields);
|
|
|
|
// Clean up and finish logging
|
|
ui->call->clear();
|
|
|
|
QDialog::accept();
|
|
}
|
|
|
|
// closeEvent is only called from the system menu close widget for a
|
|
// modeless dialog so we use the hideEvent override to store the
|
|
// window settings
|
|
void LogQSO::hideEvent(QHideEvent *e) {
|
|
storeSettings();
|
|
QDialog::hideEvent(e);
|
|
}
|
|
|
|
Q_LOGGING_CATEGORY(logqso_js8, "logqso.js8", QtWarningMsg)
|