mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
5195 lines
204 KiB
C++
5195 lines
204 KiB
C++
/**
|
|
* @file Configuration.cpp
|
|
* @brief Implementation of the Configuration dialog for the UI
|
|
*/
|
|
|
|
//
|
|
// Read me!
|
|
//
|
|
// This file defines a configuration dialog with the user. The general
|
|
// strategy is to expose agreed configuration parameters via a custom
|
|
// interface (See Configuration.h). The state exposed through this
|
|
// public interface reflects stored or derived data in the
|
|
// Configuration::impl object. The Configuration::impl structure is
|
|
// an implementation of the PIMPL (a.k.a. Cheshire Cat or compilation
|
|
// firewall) implementation hiding idiom that allows internal state to
|
|
// be completely removed from the public interface.
|
|
//
|
|
// There is a secondary level of parameter storage which reflects
|
|
// current settings UI state, these parameters are not copied to the
|
|
// state store that the public interface exposes until the
|
|
// Configuration:impl::accept() operation is successful. The accept()
|
|
// operation is tied to the settings OK button. The normal and most
|
|
// convenient place to store this intermediate settings UI state is in
|
|
// the data models of the UI controls, if that is not convenient then
|
|
// separate member variables must be used to store that state. It is
|
|
// important for the user experience that no publicly visible settings
|
|
// are changed while the settings UI are changed i.e. all settings
|
|
// changes must be deferred until the "OK" button is
|
|
// clicked. Conversely, all changes must be discarded if the settings
|
|
// UI "Cancel" button is clicked.
|
|
//
|
|
// There is a complication related to the radio interface since the
|
|
// this module offers the facility to test the radio interface. This
|
|
// test means that the public visibility to the radio being tested
|
|
// must be changed. To maintain the illusion of deferring changes
|
|
// until they are accepted, the original radio related settings are
|
|
// stored upon showing the UI and restored if the UI is dismissed by
|
|
// canceling.
|
|
//
|
|
// It should be noted that the settings UI lives as long as the
|
|
// application client that uses it does. It is simply shown and hidden
|
|
// as it is needed rather than creating it on demand. This strategy
|
|
// saves a lot of expensive UI drawing at the expense of a little
|
|
// storage and gives a convenient place to deliver settings values
|
|
// from.
|
|
//
|
|
// Here is an overview of the high level flow of this module:
|
|
//
|
|
// 1) On construction the initial environment is initialized and
|
|
// initial values for settings are read from the QSettings
|
|
// database. At this point default values for any new settings are
|
|
// established by providing a default value to the QSettings value
|
|
// queries. This should be the only place where a hard coded value for
|
|
// a settings item is defined. Any remaining one-time UI
|
|
// initialization is also done. At the end of the constructor a method
|
|
// initialize_models() is called to load the UI with the current
|
|
// settings values.
|
|
//
|
|
// 2) When the settings UI is displayed by a client calling the exec()
|
|
// operation, only temporary state need be stored as the UI state will
|
|
// already mirror the publicly visible settings state.
|
|
//
|
|
// 3) As the user makes changes to the settings UI only validation
|
|
// need be carried out since the UI control data models are used as
|
|
// the temporary store of unconfirmed settings. As some settings will
|
|
// depend on each other a validate() operation is available, this
|
|
// operation implements a check of any complex multi-field values.
|
|
//
|
|
// 4) If the user discards the settings changes by dismissing the UI
|
|
// with the "Cancel" button; the reject() operation is called. The
|
|
// reject() operation calls initialize_models() which will revert all
|
|
// the UI visible state to the values as at the initial exec()
|
|
// operation. No changes are moved into the data fields in
|
|
// Configuration::impl that reflect the settings state published by
|
|
// the public interface (Configuration.h).
|
|
//
|
|
// 5) If the user accepts the settings changes by dismissing the UI
|
|
// with the "OK" button; the accept() operation is called which calls
|
|
// the validate() operation again and, if it passes, the fields that
|
|
// are used to deliver the settings state are updated from the UI
|
|
// control models or other temporary state variables. At the end of
|
|
// the accept() operation, just before hiding the UI and returning
|
|
// control to the caller; the new settings values are stored into the
|
|
// settings database by a call to the write_settings() operation, thus
|
|
// ensuring that settings changes are saved even if the application
|
|
// crashes or is subsequently killed.
|
|
//
|
|
// 6) On destruction, which only happens when the application
|
|
// terminates, the settings are saved to the settings database by
|
|
// calling the write_settings() operation. This is largely redundant
|
|
// but is still done to save the default values of any new settings on
|
|
// an initial run.
|
|
//
|
|
// To add a new setting:
|
|
//
|
|
// 1) Update the UI with the new widget to view and change the value.
|
|
//
|
|
// 2) Add a member to Configuration::impl to store the accepted
|
|
// setting state. If the setting state is dynamic; add a new signal to
|
|
// broadcast the setting value.
|
|
//
|
|
// 3) Add a query method to the public interface (Configuration.h)
|
|
// to access the new setting value. If the settings is dynamic; this
|
|
// step is optional since value changes will be broadcast via a
|
|
// signal.
|
|
//
|
|
// 4) Add a forwarding operation to implement the new query (3) above.
|
|
//
|
|
// 5) Add a settings read call to read_settings() with a sensible
|
|
// default value. If the setting value is dynamic, add a signal emit
|
|
// call to broadcast the setting value change.
|
|
//
|
|
// 6) Add code to initialize_models() to load the widget control's
|
|
// data model with the current value.
|
|
//
|
|
// 7) If there is no convenient data model field, add a data member to
|
|
// store the proposed new value. Ensure this member has a valid value
|
|
// on exit from read_settings().
|
|
//
|
|
// 8) Add any required inter-field validation to the validate()
|
|
// operation.
|
|
//
|
|
// 9) Add code to the accept() operation to extract the setting value
|
|
// from the widget control data model and load it into the
|
|
// Configuration::impl member that reflects the publicly visible
|
|
// setting state. If the setting value is dynamic; add a signal emit
|
|
// call to broadcast any changed state of the setting.
|
|
//
|
|
// 10) Add a settings write call to save the setting value to the
|
|
// settings database.
|
|
//
|
|
|
|
#include "Configuration.h"
|
|
#include "JS8_Include/Maidenhead.h"
|
|
#include "JS8_Include/SettingsGroup.h"
|
|
#include "JS8_Include/pimpl_impl.h"
|
|
#include "JS8_Main/Bands.h"
|
|
#include "JS8_Main/CallsignValidator.h"
|
|
#include "JS8_Main/CandidateKeyFilter.h"
|
|
#include "JS8_Main/ForeignKeyDelegate.h"
|
|
#include "JS8_Main/FrequencyLineEdit.h"
|
|
#include "JS8_Main/FrequencyList.h"
|
|
#include "JS8_Main/IARURegions.h"
|
|
#include "JS8_Main/JS8MessageBox.h"
|
|
#include "JS8_Main/MetaDataRegistry.h"
|
|
#include "JS8_Main/Modes.h"
|
|
#include "JS8_Main/StationList.h"
|
|
#include "JS8_Main/Varicode.h"
|
|
#include "JS8_Main/qt_helpers.h"
|
|
#include "JS8_Network/NetworkServerLookup.h"
|
|
#include "JS8_Transceiver/Transceiver.h"
|
|
#include "JS8_Transceiver/TransceiverFactory.h"
|
|
#include "JS8_Widgets/CheckableItemComboBox.h"
|
|
#include "JS8_Widgets/LazyFillComboBox.h"
|
|
#include "ui_Configuration.h"
|
|
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QAudioDevice>
|
|
#include <QAudioInput>
|
|
#include <QClipboard>
|
|
#include <QColorDialog>
|
|
#include <QDateTime>
|
|
#include <QDateTimeEdit>
|
|
#include <QDialog>
|
|
#include <QDir>
|
|
#include <QFileDialog>
|
|
#include <QFont>
|
|
#include <QFontDatabase>
|
|
#include <QFontDialog>
|
|
#include <QFormLayout>
|
|
#include <QIntValidator>
|
|
#include <QLibraryInfo>
|
|
#include <QLineEdit>
|
|
#include <QList>
|
|
#include <QLoggingCategory>
|
|
#include <QMediaDevices>
|
|
#include <QMetaType>
|
|
#include <QNetworkInterface>
|
|
#include <QProcess>
|
|
#include <QRegularExpressionValidator>
|
|
#include <QScopedPointer>
|
|
#include <QSerialPortInfo>
|
|
#include <QSettings>
|
|
#include <QSoundEffect>
|
|
#include <QStandardItemModel>
|
|
#include <QStandardPaths>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QStringListModel>
|
|
#include <QSysInfo>
|
|
#include <QTemporaryFile>
|
|
#include <QTextStream>
|
|
#include <QThread>
|
|
#include <QTimeZone>
|
|
#include <QTimer>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
|
|
#include "moc_Configuration.cpp"
|
|
|
|
namespace {
|
|
const QRegularExpression message_alphabet{"[^\\x00-\\x1F]*"};
|
|
|
|
// Magic numbers for file validation
|
|
constexpr quint32 qrg_magic{0xadbccbdb};
|
|
constexpr quint32 qrg_version{103}; // M.mm
|
|
|
|
// Bump this versioned key every time we need to "reset" our working
|
|
// frequencies...
|
|
const char *const versionedFrequenciesSettingsKey =
|
|
"FrequenciesForRegionModes_v3";
|
|
|
|
const char *const v2FrequenciesSettingsKey =
|
|
"FrequenciesForRegionModes_01";
|
|
}// namespace
|
|
|
|
//
|
|
// Dialog to get a new Frequency item
|
|
//
|
|
class FrequencyDialog final : public QDialog {
|
|
public:
|
|
using Item = FrequencyList_v3::Item;
|
|
|
|
explicit FrequencyDialog(IARURegions *regions_model, Modes *modes_model,
|
|
QWidget *parent = nullptr)
|
|
: QDialog{parent} {
|
|
setWindowTitle(QApplication::applicationName() + " - " +
|
|
tr("Add Frequency"));
|
|
region_combo_box_.setModel(regions_model);
|
|
mode_combo_box_.setModel(modes_model);
|
|
|
|
auto form_layout = new QFormLayout();
|
|
form_layout->addRow(tr("IARU &Region:"), ®ion_combo_box_);
|
|
form_layout->addRow(tr("&Mode:"), &mode_combo_box_);
|
|
form_layout->addRow(tr("&Frequency (MHz):"), &frequency_line_edit_);
|
|
form_layout->addRow(tr("&Description:"), &description_line_edit_);
|
|
|
|
auto main_layout = new QVBoxLayout(this);
|
|
main_layout->addLayout(form_layout);
|
|
|
|
auto button_box = new QDialogButtonBox{QDialogButtonBox::Ok |
|
|
QDialogButtonBox::Cancel};
|
|
main_layout->addWidget(button_box);
|
|
|
|
connect(button_box, &QDialogButtonBox::accepted, this,
|
|
&FrequencyDialog::accept);
|
|
connect(button_box, &QDialogButtonBox::rejected, this,
|
|
&FrequencyDialog::reject);
|
|
}
|
|
|
|
Item item() const {
|
|
return {frequency_line_edit_.frequency(),
|
|
Modes::value(mode_combo_box_.currentText()),
|
|
IARURegions::value(region_combo_box_.currentText()),
|
|
description_line_edit_.text().trimmed()
|
|
};
|
|
}
|
|
|
|
private:
|
|
QComboBox region_combo_box_;
|
|
QComboBox mode_combo_box_;
|
|
FrequencyLineEdit frequency_line_edit_;
|
|
QLineEdit description_line_edit_;
|
|
};
|
|
|
|
//
|
|
// Dialog to get a new Station item
|
|
//
|
|
class StationDialog final : public QDialog {
|
|
public:
|
|
explicit StationDialog(StationList const *stations, Bands *bands,
|
|
QWidget *parent = nullptr)
|
|
: QDialog{parent},
|
|
filtered_bands_{new CandidateKeyFilter{bands, stations, 0, 0}},
|
|
all_bands_{bands} {
|
|
setWindowTitle(QApplication::applicationName() + " - " +
|
|
tr("Add Schedule"));
|
|
|
|
band_.setModel(filtered_bands_.data());
|
|
|
|
switch_at_.setTimeZone(QTimeZone::utc());
|
|
|
|
switch_at_.setDisplayFormat("hh:mm");
|
|
|
|
switch_until_.setTimeZone(QTimeZone::utc());
|
|
|
|
switch_until_.setDisplayFormat("hh:mm");
|
|
|
|
auto form_layout = new QFormLayout();
|
|
form_layout->addRow(tr("&Frequency (MHz):"), &freq_);
|
|
form_layout->addRow(tr("&Switch at (UTC):"), &switch_at_);
|
|
form_layout->addRow(tr("&Until (UTC):"), &switch_until_);
|
|
form_layout->addRow(tr("&Description:"), &description_);
|
|
|
|
auto main_layout = new QVBoxLayout(this);
|
|
main_layout->addLayout(form_layout);
|
|
|
|
auto button_box = new QDialogButtonBox{QDialogButtonBox::Ok |
|
|
QDialogButtonBox::Cancel};
|
|
main_layout->addWidget(button_box);
|
|
|
|
connect(button_box, &QDialogButtonBox::accepted, this,
|
|
&StationDialog::accept);
|
|
connect(button_box, &QDialogButtonBox::rejected, this,
|
|
&StationDialog::reject);
|
|
}
|
|
|
|
StationList::Station station() const {
|
|
auto band = all_bands_->find(freq_.frequency());
|
|
|
|
auto a =
|
|
QDateTime(QDate(2000, 1, 1), switch_at_.time(), QTimeZone::utc());
|
|
auto b = QDateTime(QDate(2000, 1, 1), switch_until_.time(),
|
|
QTimeZone::utc());
|
|
|
|
return {band, freq_.frequency(), a, b, description_.text()};
|
|
}
|
|
|
|
int exec() override {
|
|
filtered_bands_->set_active_key();
|
|
return QDialog::exec();
|
|
}
|
|
|
|
private:
|
|
QScopedPointer<CandidateKeyFilter> filtered_bands_;
|
|
Bands *all_bands_;
|
|
|
|
QComboBox band_;
|
|
FrequencyLineEdit freq_;
|
|
QTimeEdit switch_at_;
|
|
QTimeEdit switch_until_;
|
|
QLineEdit description_;
|
|
};
|
|
|
|
class RearrangableMacrosModel : public QStringListModel {
|
|
public:
|
|
Qt::ItemFlags flags(QModelIndex const &index) const override {
|
|
auto flags = QStringListModel::flags(index);
|
|
if (index.isValid()) {
|
|
// disallow drop onto existing items
|
|
flags &= ~Qt::ItemIsDropEnabled;
|
|
}
|
|
return flags;
|
|
}
|
|
};
|
|
|
|
//
|
|
// Class MessageItemDelegate
|
|
//
|
|
// Item delegate for message entry such as free text message macros.
|
|
//
|
|
class MessageItemDelegate final : public QStyledItemDelegate {
|
|
public:
|
|
using QStyledItemDelegate::QStyledItemDelegate;
|
|
|
|
QWidget *createEditor(QWidget *parent,
|
|
QStyleOptionViewItem const & /* option*/
|
|
,
|
|
QModelIndex const & /* index */
|
|
) const override {
|
|
auto editor = new QLineEdit{parent};
|
|
editor->setFrame(false);
|
|
editor->setValidator(
|
|
new QRegularExpressionValidator{message_alphabet, editor});
|
|
return editor;
|
|
}
|
|
};
|
|
|
|
// Internal implementation of the Configuration class.
|
|
class Configuration::impl final : public QDialog {
|
|
Q_OBJECT;
|
|
|
|
public:
|
|
using FrequencyDelta = Radio::FrequencyDelta;
|
|
using port_type = Configuration::port_type;
|
|
|
|
explicit impl(Configuration *self, QDir const &temp_directory,
|
|
QSettings *settings, QWidget *parent);
|
|
~impl();
|
|
|
|
bool have_rig();
|
|
|
|
void transceiver_frequency(Frequency);
|
|
void transceiver_tx_frequency(Frequency);
|
|
void transceiver_mode(MODE);
|
|
void transceiver_ptt(bool);
|
|
void sync_transceiver(bool force_signal);
|
|
|
|
Q_SLOT int exec() override;
|
|
Q_SLOT void accept() override;
|
|
Q_SLOT void reject() override;
|
|
Q_SLOT void done(int) override;
|
|
|
|
private:
|
|
typedef QList<QAudioDevice> AudioDevices;
|
|
|
|
|
|
struct AudioBinding
|
|
{
|
|
QAudioDevice::Mode mode;
|
|
QComboBox* device_combo = nullptr;
|
|
QComboBox* channel_combo = nullptr; // nullptr for notifications (no channel UI)
|
|
bool allow_both = false; // true for output, false for input
|
|
bool allow_monitor_inputs = false; // only relevant for input on Linux
|
|
|
|
// Settings keys
|
|
const char* key_id = nullptr;
|
|
const char* key_name = nullptr; // legacy/migration hint
|
|
const char* key_channel = nullptr; // nullptr for notifications
|
|
};
|
|
|
|
void read_settings();
|
|
void write_settings();
|
|
|
|
void find_audio_devices();
|
|
void resolve_audio_binding(AudioBinding const& b,
|
|
QAudioDevice& next_device,
|
|
AudioDevice::Channel* next_channel);
|
|
|
|
QAudioDevice find_audio_device(QAudioDevice::Mode mode,
|
|
QComboBox *combo_box,
|
|
QString const& saved_id,
|
|
QString const& saved_name,
|
|
bool allow_monitors);
|
|
void load_audio_devices(QAudioDevice::Mode mode,
|
|
QComboBox *combo_box,
|
|
QAudioDevice *device,
|
|
bool show_monitors = true);
|
|
void update_audio_channels(QComboBox const *, QComboBox const *, int, bool);
|
|
void load_network_interfaces(CheckableItemComboBox *, QStringList current);
|
|
|
|
static QAction *attachRequiredIndicator(
|
|
QLineEdit *edit, const QString &toolTip = QStringLiteral("Required"));
|
|
|
|
QStringList get_selected_network_interfaces(CheckableItemComboBox *);
|
|
static QString dev_id(QAudioDevice const &d);
|
|
static bool is_monitor(QAudioDevice const &d);
|
|
static bool looks_like_capture(QAudioDevice const &d);
|
|
static QString id_tail(QAudioDevice const &d);
|
|
static QString make_label(QString const &base, QAudioDevice const &d,
|
|
int dupCount);
|
|
void run_diagnostics();
|
|
QString build_diagnostics_text() const;
|
|
static QString run_cmd(QString const &program, QStringList const &args,
|
|
int timeout_ms);
|
|
static void append_env(QTextStream &ts, char const *key);
|
|
static QString dump_devices(QString const &title,
|
|
QList<QAudioDevice> const &devices);
|
|
|
|
void find_tab(QWidget *);
|
|
|
|
void initialize_models();
|
|
bool split_mode() const {
|
|
return (WSJT_RIG_NONE_CAN_SPLIT || !rig_is_dummy_) &&
|
|
(rig_params_.split_mode != TransceiverFactory::split_mode_none);
|
|
}
|
|
void set_cached_mode();
|
|
bool open_rig(bool force = false);
|
|
// bool set_mode ();
|
|
void close_rig();
|
|
TransceiverFactory::ParameterPack gather_rig_data() const;
|
|
void enumerate_rigs();
|
|
void set_rig_invariants();
|
|
bool validate();
|
|
void fill_port_combo_box(QComboBox *);
|
|
Frequency apply_calibration(Frequency) const;
|
|
Frequency remove_calibration(Frequency) const;
|
|
void updatePillPreview(QLabel *label, const QColor &bg, const QColor &fg);
|
|
void choosePillPreviewColor(QColor &target, QLabel *label,
|
|
const QColor &bg, const QColor &fg,
|
|
const QString &title);
|
|
|
|
void delete_frequencies();
|
|
void load_frequencies();
|
|
void merge_frequencies();
|
|
void save_frequencies();
|
|
void reset_frequencies();
|
|
void insert_frequency();
|
|
FrequencyList_v3::FrequencyItems read_frequencies_file(QString const &);
|
|
|
|
void delete_stations();
|
|
void insert_station();
|
|
|
|
Q_SLOT void on_psk_reporter_check_box_toggled(bool checked);
|
|
Q_SLOT void on_enable_aprs_spotting_check_box_toggled(bool checked);
|
|
Q_SLOT void on_enable_aprs_relay_check_box_toggled(bool checked);
|
|
Q_SLOT void on_notifications_check_box_toggled(bool checked);
|
|
Q_SLOT void on_font_push_button_clicked();
|
|
Q_SLOT void on_style_push_button_clicked();
|
|
Q_SLOT void on_tableFontButton_clicked();
|
|
Q_SLOT void on_PTT_port_combo_box_activated(int);
|
|
Q_SLOT void on_CAT_port_combo_box_activated(int);
|
|
Q_SLOT void on_CAT_serial_baud_combo_box_currentIndexChanged(int);
|
|
Q_SLOT void on_CAT_data_bits_button_group_buttonClicked(QAbstractButton *);
|
|
Q_SLOT void on_CAT_stop_bits_button_group_buttonClicked(QAbstractButton *);
|
|
Q_SLOT void on_CAT_handshake_button_group_buttonClicked(QAbstractButton *);
|
|
Q_SLOT void on_CAT_poll_interval_spin_box_valueChanged(int);
|
|
Q_SLOT void on_split_mode_button_group_buttonClicked(QAbstractButton *);
|
|
Q_SLOT void on_test_CAT_push_button_clicked();
|
|
Q_SLOT void on_test_PTT_push_button_clicked(bool checked);
|
|
Q_SLOT void on_force_DTR_combo_box_currentIndexChanged(int);
|
|
Q_SLOT void on_force_RTS_combo_box_currentIndexChanged(int);
|
|
Q_SLOT void on_rig_combo_box_currentIndexChanged(int);
|
|
Q_SLOT void on_add_macro_push_button_clicked(bool = false);
|
|
Q_SLOT void on_delete_macro_push_button_clicked(bool = false);
|
|
Q_SLOT void on_PTT_method_button_group_buttonClicked(QAbstractButton *);
|
|
Q_SLOT void on_groups_line_edit_textChanged(QString const &);
|
|
Q_SLOT void on_info_message_line_edit_textChanged(QString const &);
|
|
Q_SLOT void on_cq_message_line_edit_textChanged(QString const &);
|
|
Q_SLOT void on_reply_message_line_edit_textChanged(QString const &);
|
|
Q_SLOT void on_add_macro_line_edit_editingFinished();
|
|
Q_SLOT void delete_macro();
|
|
void delete_selected_macros(QModelIndexList);
|
|
Q_SLOT void on_save_path_select_push_button_clicked(bool);
|
|
Q_SLOT void on_calibration_intercept_spin_box_valueChanged(double);
|
|
Q_SLOT void on_calibration_slope_ppm_spin_box_valueChanged(double);
|
|
Q_SLOT void handle_transceiver_update(TransceiverState const &,
|
|
unsigned sequence_number);
|
|
Q_SLOT void handle_transceiver_failure(QString const &reason);
|
|
Q_SLOT void on_cqMessagesButton_clicked();
|
|
Q_SLOT void on_primaryHighlightButton_clicked();
|
|
Q_SLOT void on_secondaryHighlightButton_clicked();
|
|
Q_SLOT void on_pbMyCall_clicked();
|
|
Q_SLOT void on_tableBackgroundButton_clicked();
|
|
Q_SLOT void on_tableSelectedRowBackgroundButton_clicked();
|
|
Q_SLOT void on_tableForegroundButton_clicked();
|
|
Q_SLOT void on_rxBackgroundButton_clicked();
|
|
Q_SLOT void on_rxForegroundButton_clicked();
|
|
Q_SLOT void on_rxFontButton_clicked();
|
|
Q_SLOT void on_composeBackgroundButton_clicked();
|
|
Q_SLOT void on_composeForegroundButton_clicked();
|
|
Q_SLOT void on_composeFontButton_clicked();
|
|
Q_SLOT void on_txForegroundButton_clicked();
|
|
Q_SLOT void on_txFontButton_clicked();
|
|
Q_SLOT void on_pillRecipientBgButton_clicked();
|
|
Q_SLOT void on_pillRecipientFgButton_clicked();
|
|
Q_SLOT void on_pillCommandBgButton_clicked();
|
|
Q_SLOT void on_pillCommandFgButton_clicked();
|
|
Q_SLOT void on_pillGroupBgButton_clicked();
|
|
Q_SLOT void on_pillGroupFgButton_clicked();
|
|
Q_SLOT void on_pillSenderBgButton_clicked();
|
|
Q_SLOT void on_pillSenderFgButton_clicked();
|
|
|
|
// typenames used as arguments must match registered type names :(
|
|
Q_SIGNAL void start_transceiver(unsigned seqeunce_number) const;
|
|
Q_SIGNAL void set_transceiver(Transceiver::TransceiverState const &,
|
|
unsigned sequence_number) const;
|
|
Q_SIGNAL void stop_transceiver() const;
|
|
|
|
Configuration *const self_; // back pointer to public interface
|
|
|
|
QThread *transceiver_thread_;
|
|
TransceiverFactory transceiver_factory_;
|
|
QList<QMetaObject::Connection> rig_connections_;
|
|
|
|
QScopedPointer<Ui::configuration_dialog> ui_;
|
|
|
|
QSettings *settings_;
|
|
|
|
QDir temp_dir_;
|
|
QDir writeable_data_dir_;
|
|
QDir default_save_directory_;
|
|
QDir save_directory_;
|
|
|
|
QFont font_;
|
|
QFont next_font_;
|
|
|
|
QFont table_font_;
|
|
QFont next_table_font_;
|
|
|
|
QFont rx_text_font_;
|
|
QFont next_rx_text_font_;
|
|
|
|
QFont tx_text_font_;
|
|
QFont next_tx_text_font_;
|
|
|
|
QFont compose_text_font_;
|
|
QFont next_compose_text_font_;
|
|
|
|
bool restart_sound_input_device_;
|
|
bool restart_sound_output_device_;
|
|
bool restart_notification_sound_output_device_;
|
|
|
|
bool enable_notifications_;
|
|
QMap<QString, bool> notifications_enabled_;
|
|
QMap<QString, QString> notifications_paths_;
|
|
|
|
QStringListModel macros_;
|
|
RearrangableMacrosModel next_macros_;
|
|
QAction *macro_delete_action_;
|
|
|
|
Bands bands_;
|
|
IARURegions regions_;
|
|
IARURegions::Region region_;
|
|
Modes modes_;
|
|
FrequencyList_v3 frequencies_;
|
|
FrequencyList_v3 next_frequencies_;
|
|
StationList stations_;
|
|
StationList next_stations_;
|
|
|
|
QAction *frequency_delete_action_;
|
|
QAction *frequency_insert_action_;
|
|
QAction *load_frequencies_action_;
|
|
QAction *save_frequencies_action_;
|
|
QAction *merge_frequencies_action_;
|
|
QAction *reset_frequencies_action_;
|
|
FrequencyDialog *frequency_dialog_;
|
|
|
|
QAction *station_hop_action_;
|
|
QAction *station_delete_action_;
|
|
QAction *station_insert_action_;
|
|
StationDialog *station_dialog_;
|
|
|
|
TransceiverFactory::ParameterPack rig_params_;
|
|
TransceiverFactory::ParameterPack saved_rig_params_;
|
|
TransceiverFactory::Capabilities::PortType last_port_type_;
|
|
bool rig_is_dummy_;
|
|
bool rig_active_;
|
|
bool have_rig_;
|
|
bool rig_changed_;
|
|
TransceiverState cached_rig_state_;
|
|
int rig_resolution_; // see Transceiver::resolution signal
|
|
CalibrationParams calibration_;
|
|
bool frequency_calibration_disabled_; // not persistent
|
|
unsigned transceiver_command_number_;
|
|
QString dynamic_grid_;
|
|
QString dynamic_info_;
|
|
QString dynamic_status_;
|
|
|
|
// configuration fields that we publish
|
|
bool auto_switch_bands_;
|
|
QString my_callsign_;
|
|
QString my_grid_;
|
|
QStringList my_groups_;
|
|
QStringList auto_whitelist_;
|
|
QStringList auto_blacklist_;
|
|
QStringList hb_blacklist_;
|
|
QStringList spot_blacklist_;
|
|
QStringList rx_callsign_blocklist_;
|
|
QStringList primary_highlight_words_;
|
|
QStringList secondary_highlight_words_;
|
|
QString eot_;
|
|
QString mfi_;
|
|
QString my_info_;
|
|
QString my_status_;
|
|
QString cq_;
|
|
QString hb_;
|
|
QString reply_;
|
|
int callsign_aging_;
|
|
int activity_aging_;
|
|
QColor color_primary_highlight_;
|
|
QColor next_color_primary_highlight_;
|
|
QColor color_secondary_highlight_;
|
|
QColor next_color_secondary_highlight_;
|
|
QColor color_cq_;
|
|
QColor next_color_cq_;
|
|
QColor color_mycall_;
|
|
QColor next_color_mycall_;
|
|
|
|
QColor color_table_background_;
|
|
QColor next_color_table_background_;
|
|
QColor color_table_highlight_;
|
|
QColor next_color_table_highlight_;
|
|
QColor color_table_foreground_;
|
|
QColor next_color_table_foreground_;
|
|
|
|
QColor color_rx_background_;
|
|
QColor next_color_rx_background_;
|
|
QColor color_rx_foreground_;
|
|
QColor next_color_rx_foreground_;
|
|
QColor color_compose_background_;
|
|
QColor next_color_compose_background_;
|
|
QColor color_compose_foreground_;
|
|
QColor next_color_compose_foreground_;
|
|
QColor color_tx_foreground_;
|
|
QColor next_color_tx_foreground_;
|
|
QColor color_DXCC_;
|
|
QColor next_color_DXCC_;
|
|
QColor color_NewCall_;
|
|
QColor next_color_NewCall_;
|
|
bool pills_enabled_;
|
|
QColor color_pill_recipient_bg_;
|
|
QColor next_color_pill_recipient_bg_;
|
|
QColor color_pill_recipient_fg_;
|
|
QColor next_color_pill_recipient_fg_;
|
|
QColor color_pill_command_bg_;
|
|
QColor next_color_pill_command_bg_;
|
|
QColor color_pill_command_fg_;
|
|
QColor next_color_pill_command_fg_;
|
|
QColor color_pill_group_bg_;
|
|
QColor next_color_pill_group_bg_;
|
|
QColor color_pill_group_fg_;
|
|
QColor next_color_pill_group_fg_;
|
|
QColor color_pill_sender_bg_;
|
|
QColor next_color_pill_sender_bg_;
|
|
QColor color_pill_sender_fg_;
|
|
QColor next_color_pill_sender_fg_;
|
|
double txDelay_;
|
|
bool write_logs_;
|
|
bool reset_activity_;
|
|
bool check_for_updates_;
|
|
bool tx_qsy_allowed_;
|
|
bool spot_to_reporting_networks_;
|
|
bool spot_to_aprs_;
|
|
bool spot_to_aprs_relay_;
|
|
bool transmit_directed_;
|
|
bool autoreply_on_at_startup_;
|
|
bool autoreply_confirmation_;
|
|
bool heartbeat_anywhere_;
|
|
bool heartbeat_qso_pause_;
|
|
bool heartbeat_ack_snr_;
|
|
bool hb_rate_limit_;
|
|
bool relay_disabled_;
|
|
bool monitor_off_at_startup_;
|
|
bool transmit_off_at_startup_;
|
|
bool monitor_last_used_;
|
|
bool insert_blank_;
|
|
bool DXCC_;
|
|
bool ppfx_;
|
|
bool miles_;
|
|
bool hold_ptt_;
|
|
bool avoid_forced_identify_;
|
|
bool avoid_allcall_;
|
|
bool spellcheck_;
|
|
int heartbeat_;
|
|
int watchdog_;
|
|
bool TX_messages_;
|
|
bool use_dynamic_info_;
|
|
QString opCall_;
|
|
QString ptt_command_;
|
|
QString aprs_server_name_;
|
|
port_type aprs_server_port_;
|
|
|
|
QString udp_server_name_;
|
|
port_type udp_server_port_;
|
|
QString tcp_server_name_;
|
|
port_type tcp_server_port_;
|
|
QString n3fjp_server_name_;
|
|
port_type n3fjp_server_port_;
|
|
bool broadcast_to_n3fjp_;
|
|
QString n1mm_server_name_;
|
|
port_type n1mm_server_port_;
|
|
bool broadcast_to_n1mm_;
|
|
bool accept_udp_requests_;
|
|
bool accept_tcp_requests_;
|
|
bool udpEnabled_;
|
|
bool tcpEnabled_;
|
|
int tcpMaxConnections_;
|
|
// WSJT-X Protocol settings
|
|
bool wsjtx_protocol_enabled_;
|
|
QString wsjtx_server_name_;
|
|
port_type wsjtx_server_port_;
|
|
int wsjtx_TTL_;
|
|
bool wsjtx_accept_requests_;
|
|
QStringList wsjtx_interface_names_;
|
|
QString wsjtx_loopback_interface_name_;
|
|
DataMode data_mode_;
|
|
bool pwrBandTxMemory_;
|
|
bool pwrBandTuneMemory_;
|
|
|
|
QAudioDevice audio_input_device_;
|
|
QAudioDevice next_audio_input_device_;
|
|
AudioDevice::Channel audio_input_channel_;
|
|
AudioDevice::Channel next_audio_input_channel_;
|
|
|
|
QAudioDevice audio_output_device_;
|
|
QAudioDevice next_audio_output_device_;
|
|
AudioDevice::Channel audio_output_channel_;
|
|
AudioDevice::Channel next_audio_output_channel_;
|
|
|
|
QAudioDevice notification_audio_output_device_;
|
|
QAudioDevice next_notification_audio_output_device_;
|
|
|
|
friend class Configuration;
|
|
|
|
void hop_to_station();
|
|
};
|
|
|
|
#include "Configuration.moc"
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(configuration_js8)
|
|
|
|
// delegate to implementation class
|
|
Configuration::Configuration(QDir const &temp_directory, QSettings *settings,
|
|
QWidget *parent)
|
|
: m_{this, temp_directory, settings, parent} {}
|
|
|
|
Configuration::~Configuration() {}
|
|
|
|
QDir Configuration::writeable_data_dir() const {
|
|
return m_->writeable_data_dir_;
|
|
}
|
|
QDir Configuration::temp_dir() const { return m_->temp_dir_; }
|
|
|
|
void Configuration::select_tab(int index) {
|
|
m_->ui_->configuration_tabs->setCurrentIndex(index);
|
|
}
|
|
int Configuration::exec() { return m_->exec(); }
|
|
bool Configuration::is_active() const { return m_->isVisible(); }
|
|
|
|
QAudioDevice const &Configuration::audio_input_device() const {
|
|
return m_->audio_input_device_;
|
|
}
|
|
AudioDevice::Channel Configuration::audio_input_channel() const {
|
|
return m_->audio_input_channel_;
|
|
}
|
|
QAudioDevice const &Configuration::audio_output_device() const {
|
|
return m_->audio_output_device_;
|
|
}
|
|
AudioDevice::Channel Configuration::audio_output_channel() const {
|
|
return m_->audio_output_channel_;
|
|
}
|
|
QAudioDevice const &Configuration::notification_audio_output_device() const {
|
|
return m_->notification_audio_output_device_;
|
|
}
|
|
bool Configuration::notifications_enabled() const {
|
|
return m_->enable_notifications_;
|
|
}
|
|
QString Configuration::notification_path(const QString &key) const {
|
|
if (!m_->enable_notifications_) {
|
|
return "";
|
|
}
|
|
|
|
if (!m_->notifications_enabled_.value(key, false)) {
|
|
return "";
|
|
}
|
|
|
|
return m_->notifications_paths_.value(key, "");
|
|
}
|
|
bool Configuration::restart_audio_input() const {
|
|
return m_->restart_sound_input_device_;
|
|
}
|
|
bool Configuration::restart_audio_output() const {
|
|
return m_->restart_sound_output_device_;
|
|
}
|
|
bool Configuration::restart_notification_audio_output() const {
|
|
return m_->restart_notification_sound_output_device_;
|
|
}
|
|
bool Configuration::use_dynamic_grid() const { return m_->use_dynamic_info_; }
|
|
QString Configuration::my_callsign() const { return m_->my_callsign_; }
|
|
QColor Configuration::color_table_background() const {
|
|
return m_->color_table_background_;
|
|
}
|
|
QColor Configuration::color_table_highlight() const {
|
|
return m_->color_table_highlight_;
|
|
}
|
|
QColor Configuration::color_table_foreground() const {
|
|
return m_->color_table_foreground_;
|
|
}
|
|
QColor Configuration::color_primary_highlight() const {
|
|
return m_->color_primary_highlight_;
|
|
}
|
|
QColor Configuration::color_secondary_highlight() const {
|
|
return m_->color_secondary_highlight_;
|
|
}
|
|
QColor Configuration::color_CQ() const { return m_->color_cq_; }
|
|
QColor Configuration::color_MyCall() const { return m_->color_mycall_; }
|
|
QColor Configuration::color_rx_background() const {
|
|
return m_->color_rx_background_;
|
|
}
|
|
QColor Configuration::color_rx_foreground() const {
|
|
return m_->color_rx_foreground_;
|
|
}
|
|
QColor Configuration::color_tx_foreground() const {
|
|
return m_->color_tx_foreground_;
|
|
}
|
|
QColor Configuration::color_compose_background() const {
|
|
return m_->color_compose_background_;
|
|
}
|
|
QColor Configuration::color_compose_foreground() const {
|
|
return m_->color_compose_foreground_;
|
|
}
|
|
QColor Configuration::color_DXCC() const { return m_->color_DXCC_; }
|
|
QColor Configuration::color_NewCall() const { return m_->color_NewCall_; }
|
|
bool Configuration::pills_enabled() const { return m_->pills_enabled_; }
|
|
QColor Configuration::color_pill_recipient_bg() const { return m_->color_pill_recipient_bg_; }
|
|
QColor Configuration::color_pill_recipient_fg() const { return m_->color_pill_recipient_fg_; }
|
|
QColor Configuration::color_pill_command_bg() const { return m_->color_pill_command_bg_; }
|
|
QColor Configuration::color_pill_command_fg() const { return m_->color_pill_command_fg_; }
|
|
QColor Configuration::color_pill_group_bg() const { return m_->color_pill_group_bg_; }
|
|
QColor Configuration::color_pill_group_fg() const { return m_->color_pill_group_fg_; }
|
|
QColor Configuration::color_pill_sender_bg() const { return m_->color_pill_sender_bg_; }
|
|
QColor Configuration::color_pill_sender_fg() const { return m_->color_pill_sender_fg_; }
|
|
QFont Configuration::table_font() const { return m_->table_font_; }
|
|
QFont Configuration::text_font() const { return m_->font_; }
|
|
QFont Configuration::rx_text_font() const { return m_->rx_text_font_; }
|
|
QFont Configuration::tx_text_font() const { return m_->tx_text_font_; }
|
|
QFont Configuration::compose_text_font() const {
|
|
return m_->compose_text_font_;
|
|
}
|
|
double Configuration::txDelay() const { return m_->txDelay_; }
|
|
bool Configuration::write_logs() const { return m_->write_logs_; }
|
|
bool Configuration::reset_activity() const { return m_->reset_activity_; }
|
|
bool Configuration::check_for_updates() const { return m_->check_for_updates_; }
|
|
bool Configuration::tx_qsy_allowed() const { return m_->tx_qsy_allowed_; }
|
|
bool Configuration::spot_to_reporting_networks() const {
|
|
// rig must be open and working to spot externally
|
|
return is_transceiver_online() && m_->spot_to_reporting_networks_;
|
|
}
|
|
void Configuration::set_spot_to_reporting_networks(bool spot) {
|
|
if (m_->spot_to_reporting_networks_ != spot) {
|
|
m_->spot_to_reporting_networks_ = spot;
|
|
m_->write_settings();
|
|
}
|
|
}
|
|
|
|
bool Configuration::spot_to_aprs() const {
|
|
return spot_to_reporting_networks() && m_->spot_to_aprs_;
|
|
}
|
|
|
|
bool Configuration::spot_to_aprs_relay() const {
|
|
return spot_to_aprs() && m_->spot_to_aprs_relay_;
|
|
}
|
|
|
|
bool Configuration::transmit_directed() const { return m_->transmit_directed_; }
|
|
bool Configuration::autoreply_on_at_startup() const {
|
|
// auto-reply cannot be on at startup if the callsign or grid is empty
|
|
if (my_callsign().isEmpty() || my_grid().isEmpty()) {
|
|
return false;
|
|
}
|
|
return m_->autoreply_on_at_startup_;
|
|
}
|
|
bool Configuration::autoreply_confirmation() const {
|
|
return m_->autoreply_confirmation_;
|
|
}
|
|
void Configuration::set_autoreply_confirmation(bool enabled) {
|
|
if (m_->autoreply_confirmation_ != enabled) {
|
|
m_->autoreply_confirmation_ = enabled;
|
|
m_->write_settings();
|
|
}
|
|
}
|
|
bool Configuration::heartbeat_anywhere() const {
|
|
return m_->heartbeat_anywhere_;
|
|
}
|
|
bool Configuration::heartbeat_qso_pause() const {
|
|
return m_->heartbeat_qso_pause_;
|
|
}
|
|
bool Configuration::heartbeat_ack_snr() const {
|
|
#if JS8_HB_ACK_SNR_CONFIGURABLE
|
|
return m_->heartbeat_ack_snr_;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
bool Configuration::hb_rate_limit() const { return m_->hb_rate_limit_; }
|
|
bool Configuration::relay_off() const { return m_->relay_disabled_; }
|
|
bool Configuration::monitor_off_at_startup() const {
|
|
return m_->monitor_off_at_startup_;
|
|
}
|
|
bool Configuration::transmit_off_at_startup() const {
|
|
return m_->transmit_off_at_startup_;
|
|
}
|
|
bool Configuration::monitor_last_used() const {
|
|
return m_->rig_is_dummy_ || m_->monitor_last_used_;
|
|
}
|
|
bool Configuration::insert_blank() const { return m_->insert_blank_; }
|
|
bool Configuration::DXCC() const { return m_->DXCC_; }
|
|
bool Configuration::ppfx() const { return m_->ppfx_; }
|
|
bool Configuration::miles() const { return m_->miles_; }
|
|
bool Configuration::hold_ptt() const { return m_->hold_ptt_; }
|
|
bool Configuration::avoid_forced_identify() const {
|
|
return m_->avoid_forced_identify_;
|
|
}
|
|
bool Configuration::avoid_allcall() const { return m_->avoid_allcall_; }
|
|
void Configuration::set_avoid_allcall(bool avoid) {
|
|
if (m_->avoid_allcall_ != avoid) {
|
|
m_->avoid_allcall_ = avoid;
|
|
m_->write_settings();
|
|
}
|
|
}
|
|
bool Configuration::spellcheck() const { return m_->spellcheck_; }
|
|
int Configuration::heartbeat() const { return m_->heartbeat_; }
|
|
int Configuration::watchdog() const { return m_->watchdog_; }
|
|
bool Configuration::TX_messages() const { return m_->TX_messages_; }
|
|
bool Configuration::split_mode() const { return m_->split_mode(); }
|
|
QString Configuration::opCall() const { return m_->opCall_; }
|
|
QString Configuration::ptt_command() const {
|
|
return m_->ptt_command_.trimmed();
|
|
}
|
|
QString Configuration::aprs_server_name() const {
|
|
return m_->aprs_server_name_;
|
|
}
|
|
auto Configuration::aprs_server_port() const -> port_type {
|
|
return m_->aprs_server_port_;
|
|
}
|
|
QString Configuration::udp_server_name() const { return m_->udp_server_name_; }
|
|
auto Configuration::udp_server_port() const -> port_type {
|
|
return m_->udp_server_port_;
|
|
}
|
|
QString Configuration::tcp_server_name() const { return m_->tcp_server_name_; }
|
|
auto Configuration::tcp_server_port() const -> port_type {
|
|
return m_->tcp_server_port_;
|
|
}
|
|
bool Configuration::accept_udp_requests() const {
|
|
return m_->accept_udp_requests_;
|
|
}
|
|
bool Configuration::accept_tcp_requests() const {
|
|
return m_->accept_tcp_requests_;
|
|
}
|
|
// WSJT-X Protocol accessors
|
|
bool Configuration::wsjtx_protocol_enabled() const {
|
|
return m_->wsjtx_protocol_enabled_;
|
|
}
|
|
QString Configuration::wsjtx_server_name() const {
|
|
return m_->wsjtx_server_name_;
|
|
}
|
|
auto Configuration::wsjtx_server_port() const -> port_type {
|
|
return m_->wsjtx_server_port_;
|
|
}
|
|
int Configuration::wsjtx_TTL() const { return m_->wsjtx_TTL_; }
|
|
bool Configuration::wsjtx_accept_requests() const {
|
|
return m_->wsjtx_accept_requests_;
|
|
}
|
|
QStringList Configuration::wsjtx_interface_names() const {
|
|
return m_->wsjtx_interface_names_;
|
|
}
|
|
QString Configuration::n3fjp_server_name() const {
|
|
return m_->n3fjp_server_name_;
|
|
}
|
|
auto Configuration::n3fjp_server_port() const -> port_type {
|
|
return m_->n3fjp_server_port_;
|
|
}
|
|
bool Configuration::broadcast_to_n3fjp() const {
|
|
return m_->broadcast_to_n3fjp_;
|
|
}
|
|
QString Configuration::n1mm_server_name() const {
|
|
return m_->n1mm_server_name_;
|
|
}
|
|
auto Configuration::n1mm_server_port() const -> port_type {
|
|
return m_->n1mm_server_port_;
|
|
}
|
|
bool Configuration::broadcast_to_n1mm() const { return m_->broadcast_to_n1mm_; }
|
|
bool Configuration::udpEnabled() const { return m_->udpEnabled_; }
|
|
bool Configuration::tcpEnabled() const { return m_->tcpEnabled_; }
|
|
int Configuration::tcp_max_connections() const {
|
|
return m_->tcpMaxConnections_;
|
|
}
|
|
Bands *Configuration::bands() { return &m_->bands_; }
|
|
Bands const *Configuration::bands() const { return &m_->bands_; }
|
|
StationList *Configuration::stations() { return &m_->stations_; }
|
|
StationList const *Configuration::stations() const { return &m_->stations_; }
|
|
bool Configuration::auto_switch_bands() const { return m_->auto_switch_bands_; }
|
|
IARURegions::Region Configuration::region() const { return m_->region_; }
|
|
FrequencyList_v3 *Configuration::frequencies() { return &m_->frequencies_; }
|
|
FrequencyList_v3 const *Configuration::frequencies() const {
|
|
return &m_->frequencies_;
|
|
}
|
|
QStringListModel *Configuration::macros() { return &m_->macros_; }
|
|
QStringListModel const *Configuration::macros() const { return &m_->macros_; }
|
|
QDir Configuration::save_directory() const { return m_->save_directory_; }
|
|
QString Configuration::rig_name() const { return m_->rig_params_.rig_name; }
|
|
bool Configuration::pwrBandTxMemory() const { return m_->pwrBandTxMemory_; }
|
|
bool Configuration::pwrBandTuneMemory() const { return m_->pwrBandTuneMemory_; }
|
|
|
|
void Configuration::set_calibration(CalibrationParams params) {
|
|
m_->calibration_ = params;
|
|
}
|
|
|
|
void Configuration::enable_calibration(bool on) {
|
|
auto target_frequency =
|
|
m_->remove_calibration(m_->cached_rig_state_.frequency());
|
|
m_->frequency_calibration_disabled_ = !on;
|
|
transceiver_frequency(target_frequency);
|
|
}
|
|
|
|
bool Configuration::is_transceiver_online() const { return m_->rig_active_; }
|
|
|
|
bool Configuration::is_dummy_rig() const { return m_->rig_is_dummy_; }
|
|
|
|
bool Configuration::transceiver_online() {
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::transceiver_online: " << m_->cached_rig_state_;
|
|
return m_->have_rig();
|
|
}
|
|
|
|
int Configuration::transceiver_resolution() const {
|
|
return m_->rig_resolution_;
|
|
}
|
|
|
|
void Configuration::transceiver_offline() {
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::transceiver_offline:" << m_->cached_rig_state_;
|
|
m_->close_rig();
|
|
}
|
|
|
|
void Configuration::transceiver_frequency(Frequency f) {
|
|
qCDebug(configuration_js8) << "Configuration::transceiver_frequency:" << f
|
|
<< m_->cached_rig_state_;
|
|
m_->transceiver_frequency(f);
|
|
}
|
|
|
|
void Configuration::transceiver_tx_frequency(Frequency f) {
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::transceiver_tx_frequency:" << f
|
|
<< m_->cached_rig_state_;
|
|
m_->transceiver_tx_frequency(f);
|
|
}
|
|
|
|
void Configuration::transceiver_mode(MODE mode) {
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::transceiver_mode:" << mode << m_->cached_rig_state_;
|
|
m_->transceiver_mode(mode);
|
|
}
|
|
|
|
void Configuration::transceiver_ptt(bool on) {
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::transceiver_ptt:" << on << m_->cached_rig_state_;
|
|
|
|
m_->transceiver_ptt(on);
|
|
|
|
if (auto cmd = ptt_command(); !cmd.isEmpty()) {
|
|
|
|
if (!cmd.contains("%1"))
|
|
cmd.append(" %1");
|
|
|
|
if (auto arguments =
|
|
QProcess::splitCommand(cmd.arg(on ? "\"on\"" : "\"off\""));
|
|
!arguments.isEmpty()) {
|
|
QString program = arguments.takeFirst();
|
|
QProcess::startDetached(program, arguments);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Configuration::sync_transceiver(bool force_signal,
|
|
bool enforce_mode_and_split) {
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::sync_transceiver: force signal:" << force_signal
|
|
<< "enforce_mode_and_split:" << enforce_mode_and_split
|
|
<< m_->cached_rig_state_;
|
|
|
|
m_->sync_transceiver(force_signal);
|
|
if (!enforce_mode_and_split) {
|
|
m_->transceiver_tx_frequency(0);
|
|
}
|
|
}
|
|
|
|
void Configuration::invalidate_audio_input_device(QString /* error */) {
|
|
m_->audio_input_device_ = QAudioDevice{};
|
|
}
|
|
|
|
void Configuration::invalidate_audio_output_device(QString /* error */) {
|
|
m_->audio_output_device_ = QAudioDevice{};
|
|
}
|
|
|
|
void Configuration::invalidate_notification_audio_output_device(
|
|
QString /* error */) {
|
|
m_->notification_audio_output_device_ = QAudioDevice{};
|
|
}
|
|
|
|
bool Configuration::valid_n3fjp_info() const {
|
|
// do very rudimentary checking on the n3fjp server name and port number.
|
|
//
|
|
auto server_name = m_->n3fjp_server_name_;
|
|
auto port_number = m_->n3fjp_server_port_;
|
|
return (!(server_name.trimmed().isEmpty() || port_number == 0));
|
|
}
|
|
|
|
bool Configuration::valid_n1mm_info() const {
|
|
// do very rudimentary checking on the n1mm server name and port number.
|
|
//
|
|
auto server_name = m_->n1mm_server_name_;
|
|
auto port_number = m_->n1mm_server_port_;
|
|
return (!(server_name.trimmed().isEmpty() || port_number == 0));
|
|
}
|
|
|
|
QString Configuration::my_grid() const {
|
|
auto grid = m_->my_grid_;
|
|
if (m_->use_dynamic_info_ && m_->dynamic_grid_.size() >= 4) {
|
|
grid = m_->dynamic_grid_;
|
|
}
|
|
return grid.trimmed();
|
|
}
|
|
|
|
QSet<QString> Configuration::my_groups() const {
|
|
return QSet<QString>(m_->my_groups_.begin(), m_->my_groups_.end());
|
|
}
|
|
|
|
void Configuration::addGroup(QString const &group) {
|
|
if (!Varicode::isGroupAllowed(group)) {
|
|
JS8MessageBox::critical_message(
|
|
m_->window(),
|
|
QString("%1 is a group that cannot be joined").arg(group));
|
|
return;
|
|
}
|
|
if (!Varicode::isCompoundCallsign(group)) {
|
|
JS8MessageBox::critical_message(
|
|
m_->window(), QString("%1 is not a valid group").arg(group));
|
|
return;
|
|
}
|
|
QSet<QString> groups = my_groups();
|
|
groups.insert(group.trimmed());
|
|
m_->my_groups_ = groups.values();
|
|
m_->write_settings();
|
|
}
|
|
|
|
void Configuration::removeGroup(QString const &group) {
|
|
QSet<QString> groups = my_groups();
|
|
groups.remove(group.trimmed());
|
|
m_->my_groups_ = groups.values();
|
|
m_->write_settings();
|
|
}
|
|
|
|
void Configuration::setMyGroups(QStringList const &groups) {
|
|
m_->my_groups_ = groups;
|
|
m_->write_settings();
|
|
}
|
|
|
|
QSet<QString> Configuration::auto_whitelist() const {
|
|
return QSet<QString>(m_->auto_whitelist_.begin(),
|
|
m_->auto_whitelist_.end());
|
|
}
|
|
|
|
QSet<QString> Configuration::auto_blacklist() const {
|
|
return QSet<QString>(m_->auto_blacklist_.begin(),
|
|
m_->auto_blacklist_.end());
|
|
}
|
|
|
|
// automatic HB ACK suppression, adds callsigns to the blacklist
|
|
// that send out HB's at intervals less than 55 minutes
|
|
void Configuration::addToHbBlacklist(const QString &callsign) {
|
|
if (!m_->hb_blacklist_.contains(callsign)) {
|
|
m_->hb_blacklist_.append(callsign);
|
|
m_->write_settings();
|
|
}
|
|
}
|
|
|
|
QSet<QString> Configuration::hb_blacklist() const {
|
|
return QSet<QString>(m_->hb_blacklist_.begin(), m_->hb_blacklist_.end());
|
|
}
|
|
|
|
QSet<QString> Configuration::spot_blacklist() const {
|
|
return QSet<QString>(m_->spot_blacklist_.begin(),
|
|
m_->spot_blacklist_.end());
|
|
}
|
|
|
|
QSet<QString> Configuration::rx_callsign_blocklist() const {
|
|
return QSet<QString>(m_->rx_callsign_blocklist_.begin(),
|
|
m_->rx_callsign_blocklist_.end());
|
|
}
|
|
|
|
QSet<QString> Configuration::primary_highlight_words() const {
|
|
return QSet<QString>(m_->primary_highlight_words_.begin(),
|
|
m_->primary_highlight_words_.end());
|
|
}
|
|
|
|
QSet<QString> Configuration::secondary_highlight_words() const {
|
|
return QSet<QString>(m_->secondary_highlight_words_.begin(),
|
|
m_->secondary_highlight_words_.end());
|
|
}
|
|
|
|
QString Configuration::eot() const { return m_->eot_; }
|
|
|
|
QString Configuration::mfi() const { return m_->mfi_; }
|
|
|
|
QString Configuration::my_info() const {
|
|
auto info = m_->my_info_;
|
|
if (m_->use_dynamic_info_ && !m_->dynamic_info_.isEmpty()) {
|
|
info = m_->dynamic_info_;
|
|
}
|
|
|
|
return info.trimmed();
|
|
}
|
|
|
|
QString Configuration::my_status() const {
|
|
auto status = m_->my_status_;
|
|
if (m_->use_dynamic_info_ && !m_->dynamic_status_.isEmpty()) {
|
|
status = m_->dynamic_status_;
|
|
}
|
|
|
|
return status.trimmed();
|
|
}
|
|
|
|
QString Configuration::hb_message() const { return m_->hb_.trimmed(); }
|
|
|
|
QString Configuration::cq_message() const {
|
|
return m_->cq_.trimmed().replace("CQCQCQ", "CQ CQ CQ"); // deprecate legacy
|
|
}
|
|
|
|
QString Configuration::reply_message() const { return m_->reply_.trimmed(); }
|
|
|
|
int Configuration::callsign_aging() const { return m_->callsign_aging_; }
|
|
|
|
int Configuration::activity_aging() const { return m_->activity_aging_; }
|
|
|
|
void Configuration::set_dynamic_location(QString const &grid_descriptor) {
|
|
m_->dynamic_grid_ = grid_descriptor.trimmed();
|
|
}
|
|
|
|
void Configuration::set_dynamic_station_info(QString const &info) {
|
|
m_->dynamic_info_ = info.trimmed();
|
|
}
|
|
|
|
void Configuration::set_dynamic_station_status(QString const &status) {
|
|
m_->dynamic_status_ = status.trimmed();
|
|
}
|
|
|
|
template <typename T> void setUppercase(T *t) {
|
|
auto f = t->font();
|
|
f.setCapitalization(QFont::AllUppercase);
|
|
t->setFont(f);
|
|
}
|
|
|
|
QWidget *centeredCheckBox(QWidget *parent, QCheckBox **ppCheckbox) {
|
|
auto w = new QWidget(parent);
|
|
auto cb = new QCheckBox(parent);
|
|
auto l = new QHBoxLayout(w);
|
|
l->setContentsMargins(1, 1, 1, 1);
|
|
l->setAlignment(Qt::AlignCenter);
|
|
l->addWidget(cb);
|
|
w->setLayout(l);
|
|
if (ppCheckbox)
|
|
*ppCheckbox = cb;
|
|
return w;
|
|
}
|
|
|
|
Configuration::impl::impl(Configuration *self, QDir const &temp_directory,
|
|
QSettings *settings, QWidget *parent)
|
|
: QDialog{parent}, self_{self}, transceiver_thread_{nullptr},
|
|
ui_{new Ui::configuration_dialog}, settings_{settings},
|
|
temp_dir_{temp_directory},
|
|
writeable_data_dir_{QStandardPaths::writableLocation(
|
|
QStandardPaths::AppLocalDataLocation)},
|
|
restart_sound_input_device_{false}, restart_sound_output_device_{false},
|
|
restart_notification_sound_output_device_{false}, frequencies_{&bands_},
|
|
next_frequencies_{&bands_}, stations_{&bands_}, next_stations_{&bands_},
|
|
frequency_dialog_{new FrequencyDialog{®ions_, &modes_, this}},
|
|
station_dialog_{new StationDialog{&next_stations_, &bands_, this}},
|
|
last_port_type_{TransceiverFactory::Capabilities::none},
|
|
rig_is_dummy_{false}, rig_active_{false}, have_rig_{false},
|
|
rig_changed_{false}, rig_resolution_{0},
|
|
frequency_calibration_disabled_{false}, transceiver_command_number_{0},
|
|
spot_to_aprs_relay_{false} {
|
|
ui_->setupUi(this);
|
|
|
|
// ui_->groupBox_6->setVisible(false); //### Temporary ??? ###
|
|
|
|
{
|
|
// Find a suitable data file location
|
|
if (!writeable_data_dir_.mkpath(".")) {
|
|
JS8MessageBox::critical_message(
|
|
this, tr("Failed to create data directory"),
|
|
tr("path: \"%1\"").arg(writeable_data_dir_.absolutePath()));
|
|
throw std::runtime_error{"Failed to create data directory"};
|
|
}
|
|
|
|
// Make sure the default save directory exists
|
|
QString save_dir{"save"};
|
|
default_save_directory_ = writeable_data_dir_;
|
|
if (!default_save_directory_.mkpath(save_dir) ||
|
|
!default_save_directory_.cd(save_dir)) {
|
|
JS8MessageBox::critical_message(
|
|
this, tr("Failed to create save directory"),
|
|
tr("path: \"%1\%")
|
|
.arg(default_save_directory_.absoluteFilePath(save_dir)));
|
|
throw std::runtime_error{"Failed to create save directory"};
|
|
}
|
|
|
|
// we now have a deafult save path that exists
|
|
|
|
// make sure samples directory exists
|
|
QString samples_dir{"samples"};
|
|
if (!default_save_directory_.mkpath(samples_dir)) {
|
|
JS8MessageBox::critical_message(
|
|
this, tr("Failed to create samples directory"),
|
|
tr("path: \"%1\"")
|
|
.arg(
|
|
default_save_directory_.absoluteFilePath(samples_dir)));
|
|
throw std::runtime_error{"Failed to create samples directory"};
|
|
}
|
|
|
|
QString messages_dir{"messages"};
|
|
if (!default_save_directory_.mkpath(messages_dir)) {
|
|
JS8MessageBox::critical_message(
|
|
this, tr("Failed to create messages directory"),
|
|
tr("path: \"%1\"")
|
|
.arg(default_save_directory_.absoluteFilePath(
|
|
messages_dir)));
|
|
throw std::runtime_error{"Failed to create messages directory"};
|
|
}
|
|
|
|
// copy in any new sample files to the sample directory
|
|
QDir dest_dir{default_save_directory_};
|
|
dest_dir.cd(samples_dir);
|
|
|
|
QDir source_dir{":/" + samples_dir};
|
|
source_dir.cd(save_dir);
|
|
source_dir.cd(samples_dir);
|
|
auto list = source_dir.entryInfoList(QStringList{{"*.wav"}},
|
|
QDir::Files | QDir::Readable);
|
|
Q_FOREACH (auto const &item, list) {
|
|
if (!dest_dir.exists(item.fileName())) {
|
|
QFile file{item.absoluteFilePath()};
|
|
file.copy(dest_dir.absoluteFilePath(item.fileName()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// this must be done after the default paths above are set
|
|
read_settings();
|
|
|
|
// set up dynamic loading of audio devices
|
|
|
|
connect(
|
|
ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup,
|
|
[this]() {
|
|
QGuiApplication::setOverrideCursor(QCursor{Qt::WaitCursor});
|
|
|
|
const bool show_monitors =
|
|
settings_->value("AllowMonitorInputs", true).toBool();
|
|
|
|
load_audio_devices(QAudioDevice::Input,
|
|
ui_->sound_input_combo_box,
|
|
&next_audio_input_device_,
|
|
show_monitors);
|
|
|
|
update_audio_channels(ui_->sound_input_combo_box,
|
|
ui_->sound_input_channel_combo_box,
|
|
ui_->sound_input_combo_box->currentIndex(),
|
|
false);
|
|
|
|
ui_->sound_input_channel_combo_box->setCurrentIndex(next_audio_input_channel_);
|
|
|
|
QGuiApplication::restoreOverrideCursor();
|
|
});
|
|
|
|
connect(ui_->sound_output_combo_box, &LazyFillComboBox::about_to_show_popup,
|
|
[this]() {
|
|
QGuiApplication::setOverrideCursor(QCursor{Qt::WaitCursor});
|
|
|
|
load_audio_devices(QAudioDevice::Output,
|
|
ui_->sound_output_combo_box,
|
|
&next_audio_output_device_);
|
|
|
|
update_audio_channels(ui_->sound_output_combo_box,
|
|
ui_->sound_output_channel_combo_box,
|
|
ui_->sound_output_combo_box->currentIndex(),
|
|
true);
|
|
|
|
ui_->sound_output_channel_combo_box->setCurrentIndex(next_audio_output_channel_);
|
|
|
|
QGuiApplication::restoreOverrideCursor();
|
|
});
|
|
|
|
connect(ui_->notification_sound_output_combo_box,
|
|
&LazyFillComboBox::about_to_show_popup, [this]() {
|
|
QGuiApplication::setOverrideCursor(QCursor{Qt::WaitCursor});
|
|
|
|
load_audio_devices(QAudioDevice::Output,
|
|
ui_->notification_sound_output_combo_box,
|
|
&next_notification_audio_output_device_);
|
|
|
|
QGuiApplication::restoreOverrideCursor();
|
|
});
|
|
|
|
connect(ui_->run_diagnostics_button, &QPushButton::clicked, [this]() {
|
|
run_diagnostics();
|
|
});
|
|
|
|
connect(ui_->copy_diagnostics_button, &QPushButton::clicked, [this]() {
|
|
const auto text = ui_->diagnostics_text->toPlainText();
|
|
QGuiApplication::clipboard()->setText(text);
|
|
});
|
|
|
|
QFont f = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
f.setPointSizeF(f.pointSizeF() * 0.95);
|
|
ui_->diagnostics_text->setFont(f);
|
|
|
|
//
|
|
// validation
|
|
//
|
|
ui_->callsign_line_edit->setValidator(new CallsignValidator{this});
|
|
ui_->grid_line_edit->setValidator(new Maidenhead::ExtendedValidator{this});
|
|
|
|
// theme-aware indication for invalid callsign and grid fields
|
|
attachRequiredIndicator(ui_->callsign_line_edit, "Callsign Required");
|
|
attachRequiredIndicator(ui_->grid_line_edit, "Grid Required");
|
|
|
|
ui_->add_macro_line_edit->setValidator(
|
|
new QRegularExpressionValidator{message_alphabet, this});
|
|
ui_->info_message_line_edit->setValidator(
|
|
new QRegularExpressionValidator{message_alphabet, this});
|
|
ui_->reply_message_line_edit->setValidator(
|
|
new QRegularExpressionValidator{message_alphabet, this});
|
|
ui_->cq_message_line_edit->setValidator(
|
|
new QRegularExpressionValidator{message_alphabet, this});
|
|
ui_->hb_message_line_edit->setValidator(
|
|
new QRegularExpressionValidator{message_alphabet, this});
|
|
ui_->status_message_line_edit->setValidator(
|
|
new QRegularExpressionValidator{message_alphabet, this});
|
|
ui_->groups_line_edit->setValidator(
|
|
new QRegularExpressionValidator{message_alphabet, this});
|
|
|
|
setUppercase(ui_->callsign_line_edit);
|
|
setUppercase(ui_->grid_line_edit);
|
|
setUppercase(ui_->add_macro_line_edit);
|
|
setUppercase(ui_->info_message_line_edit);
|
|
setUppercase(ui_->status_message_line_edit);
|
|
setUppercase(ui_->reply_message_line_edit);
|
|
setUppercase(ui_->cq_message_line_edit);
|
|
setUppercase(ui_->hb_message_line_edit);
|
|
setUppercase(ui_->groups_line_edit);
|
|
setUppercase(ui_->auto_whitelist_line_edit);
|
|
|
|
ui_->udp_server_port_spin_box->setMinimum(0);
|
|
ui_->udp_server_port_spin_box->setMaximum(
|
|
std::numeric_limits<port_type>::max());
|
|
|
|
ui_->n3fjp_server_port_spin_box->setMinimum(0);
|
|
ui_->n3fjp_server_port_spin_box->setMaximum(
|
|
std::numeric_limits<port_type>::max());
|
|
|
|
ui_->n1mm_server_port_spin_box->setMinimum(0);
|
|
ui_->n1mm_server_port_spin_box->setMaximum(
|
|
std::numeric_limits<port_type>::max());
|
|
|
|
//
|
|
// assign ids to radio buttons
|
|
//
|
|
ui_->CAT_data_bits_button_group->setId(
|
|
ui_->CAT_default_bit_radio_button,
|
|
TransceiverFactory::default_data_bits);
|
|
ui_->CAT_data_bits_button_group->setId(ui_->CAT_7_bit_radio_button,
|
|
TransceiverFactory::seven_data_bits);
|
|
ui_->CAT_data_bits_button_group->setId(ui_->CAT_8_bit_radio_button,
|
|
TransceiverFactory::eight_data_bits);
|
|
|
|
ui_->CAT_stop_bits_button_group->setId(
|
|
ui_->CAT_default_stop_bit_radio_button,
|
|
TransceiverFactory::default_stop_bits);
|
|
ui_->CAT_stop_bits_button_group->setId(ui_->CAT_one_stop_bit_radio_button,
|
|
TransceiverFactory::one_stop_bit);
|
|
ui_->CAT_stop_bits_button_group->setId(ui_->CAT_two_stop_bit_radio_button,
|
|
TransceiverFactory::two_stop_bits);
|
|
|
|
ui_->CAT_handshake_button_group->setId(
|
|
ui_->CAT_handshake_default_radio_button,
|
|
TransceiverFactory::handshake_default);
|
|
ui_->CAT_handshake_button_group->setId(ui_->CAT_handshake_none_radio_button,
|
|
TransceiverFactory::handshake_none);
|
|
ui_->CAT_handshake_button_group->setId(
|
|
ui_->CAT_handshake_xon_radio_button,
|
|
TransceiverFactory::handshake_XonXoff);
|
|
ui_->CAT_handshake_button_group->setId(
|
|
ui_->CAT_handshake_hardware_radio_button,
|
|
TransceiverFactory::handshake_hardware);
|
|
|
|
ui_->PTT_method_button_group->setId(ui_->PTT_VOX_radio_button,
|
|
TransceiverFactory::PTT_method_VOX);
|
|
ui_->PTT_method_button_group->setId(ui_->PTT_CAT_radio_button,
|
|
TransceiverFactory::PTT_method_CAT);
|
|
ui_->PTT_method_button_group->setId(ui_->PTT_DTR_radio_button,
|
|
TransceiverFactory::PTT_method_DTR);
|
|
ui_->PTT_method_button_group->setId(ui_->PTT_RTS_radio_button,
|
|
TransceiverFactory::PTT_method_RTS);
|
|
|
|
ui_->TX_audio_source_button_group->setId(
|
|
ui_->TX_source_mic_radio_button,
|
|
TransceiverFactory::TX_audio_source_front);
|
|
ui_->TX_audio_source_button_group->setId(
|
|
ui_->TX_source_data_radio_button,
|
|
TransceiverFactory::TX_audio_source_rear);
|
|
|
|
ui_->TX_mode_button_group->setId(ui_->mode_none_radio_button,
|
|
data_mode_none);
|
|
ui_->TX_mode_button_group->setId(ui_->mode_USB_radio_button, data_mode_USB);
|
|
ui_->TX_mode_button_group->setId(ui_->mode_data_radio_button,
|
|
data_mode_data);
|
|
|
|
ui_->split_mode_button_group->setId(ui_->split_none_radio_button,
|
|
TransceiverFactory::split_mode_none);
|
|
ui_->split_mode_button_group->setId(ui_->split_rig_radio_button,
|
|
TransceiverFactory::split_mode_rig);
|
|
ui_->split_mode_button_group->setId(ui_->split_emulate_radio_button,
|
|
TransceiverFactory::split_mode_emulate);
|
|
|
|
//
|
|
// setup PTT port combo box drop down content
|
|
//
|
|
fill_port_combo_box(ui_->PTT_port_combo_box);
|
|
ui_->PTT_port_combo_box->addItem("CAT");
|
|
|
|
//
|
|
// setup hooks to keep audio channels aligned with devices
|
|
//
|
|
|
|
connect(
|
|
ui_->sound_input_combo_box,
|
|
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
|
[this](auto const index) {
|
|
update_audio_channels(ui_->sound_input_combo_box,
|
|
ui_->sound_input_channel_combo_box, index,
|
|
false);
|
|
});
|
|
|
|
connect(
|
|
ui_->sound_output_combo_box,
|
|
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
|
[this](auto const index) {
|
|
update_audio_channels(ui_->sound_output_combo_box,
|
|
ui_->sound_output_channel_combo_box, index,
|
|
true);
|
|
});
|
|
|
|
//
|
|
// setup macros list view
|
|
//
|
|
ui_->macros_list_view->setModel(&next_macros_);
|
|
ui_->macros_list_view->setItemDelegate(new MessageItemDelegate{this});
|
|
|
|
macro_delete_action_ = new QAction{tr("&Delete"), ui_->macros_list_view};
|
|
ui_->macros_list_view->insertAction(nullptr, macro_delete_action_);
|
|
connect(macro_delete_action_, &QAction::triggered, this,
|
|
&Configuration::impl::delete_macro);
|
|
|
|
// setup IARU region combo box model
|
|
ui_->region_combo_box->setModel(®ions_);
|
|
|
|
//
|
|
// setup working frequencies table model & view
|
|
//
|
|
frequencies_.sort(FrequencyList_v3::frequency_column);
|
|
|
|
ui_->frequencies_table_view->setModel(&next_frequencies_);
|
|
ui_->frequencies_table_view->sortByColumn(
|
|
FrequencyList_v3::frequency_column, Qt::AscendingOrder);
|
|
ui_->frequencies_table_view->setColumnHidden(
|
|
FrequencyList_v3::frequency_mhz_column, true);
|
|
|
|
// delegates
|
|
auto frequencies_item_delegate = new QStyledItemDelegate{this};
|
|
frequencies_item_delegate->setItemEditorFactory(item_editor_factory());
|
|
ui_->frequencies_table_view->setItemDelegate(frequencies_item_delegate);
|
|
ui_->frequencies_table_view->setItemDelegateForColumn(
|
|
FrequencyList_v3::region_column,
|
|
new ForeignKeyDelegate{®ions_, 0, this});
|
|
ui_->frequencies_table_view->setItemDelegateForColumn(
|
|
FrequencyList_v3::mode_column,
|
|
new ForeignKeyDelegate{&modes_, 0, this});
|
|
|
|
// actions
|
|
frequency_delete_action_ =
|
|
new QAction{tr("&Delete"), ui_->frequencies_table_view};
|
|
|
|
frequency_delete_action_->setShortcut(QKeySequence(Qt::Key_Delete));
|
|
frequency_delete_action_->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
|
|
|
ui_->frequencies_table_view->insertAction(nullptr,
|
|
frequency_delete_action_);
|
|
connect(frequency_delete_action_, &QAction::triggered, this,
|
|
&Configuration::impl::delete_frequencies);
|
|
|
|
frequency_insert_action_ =
|
|
new QAction{tr("&Insert ..."), ui_->frequencies_table_view};
|
|
ui_->frequencies_table_view->insertAction(nullptr,
|
|
frequency_insert_action_);
|
|
connect(frequency_insert_action_, &QAction::triggered, this,
|
|
&Configuration::impl::insert_frequency);
|
|
|
|
load_frequencies_action_ =
|
|
new QAction{tr("&Load ..."), ui_->frequencies_table_view};
|
|
ui_->frequencies_table_view->insertAction(nullptr,
|
|
load_frequencies_action_);
|
|
connect(load_frequencies_action_, &QAction::triggered, this,
|
|
&Configuration::impl::load_frequencies);
|
|
|
|
save_frequencies_action_ =
|
|
new QAction{tr("&Save as ..."), ui_->frequencies_table_view};
|
|
ui_->frequencies_table_view->insertAction(nullptr,
|
|
save_frequencies_action_);
|
|
connect(save_frequencies_action_, &QAction::triggered, this,
|
|
&Configuration::impl::save_frequencies);
|
|
|
|
merge_frequencies_action_ =
|
|
new QAction{tr("&Merge ..."), ui_->frequencies_table_view};
|
|
ui_->frequencies_table_view->insertAction(nullptr,
|
|
merge_frequencies_action_);
|
|
connect(merge_frequencies_action_, &QAction::triggered, this,
|
|
&Configuration::impl::merge_frequencies);
|
|
|
|
reset_frequencies_action_ =
|
|
new QAction{tr("&Reset"), ui_->frequencies_table_view};
|
|
ui_->frequencies_table_view->insertAction(nullptr,
|
|
reset_frequencies_action_);
|
|
connect(reset_frequencies_action_, &QAction::triggered, this,
|
|
&Configuration::impl::reset_frequencies);
|
|
|
|
//
|
|
// setup stations table model & view
|
|
//
|
|
stations_.sort(StationList::switch_at_column);
|
|
|
|
ui_->stations_table_view->setModel(&next_stations_);
|
|
ui_->stations_table_view->sortByColumn(StationList::switch_at_column,
|
|
Qt::AscendingOrder);
|
|
connect(ui_->auto_switch_bands_check_box, &QCheckBox::clicked,
|
|
ui_->stations_table_view, &QTableView::setEnabled);
|
|
|
|
// Immediately emit the auto switch band change signal to kick the scheduler
|
|
// without having to potentially save twice
|
|
connect(ui_->auto_switch_bands_check_box, &QCheckBox::clicked, this,
|
|
[this](bool auto_switch_bands) {
|
|
Q_EMIT self_->auto_switch_bands_changed(auto_switch_bands);
|
|
});
|
|
|
|
// delegates
|
|
auto stations_item_delegate = new QStyledItemDelegate{this};
|
|
stations_item_delegate->setItemEditorFactory(item_editor_factory());
|
|
ui_->stations_table_view->setItemDelegate(stations_item_delegate);
|
|
// ui_->stations_table_view->setItemDelegateForColumn
|
|
// (StationList::band_column, new ForeignKeyDelegate {&bands_,
|
|
// &next_stations_, 0, StationList::band_column, this});
|
|
|
|
ui_->stations_table_view->resizeColumnToContents(StationList::band_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::frequency_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::switch_at_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::switch_until_column);
|
|
ui_->stations_table_view->horizontalHeader()->setSectionResizeMode(
|
|
QHeaderView::ResizeToContents);
|
|
|
|
// actions
|
|
station_hop_action_ =
|
|
new QAction{tr("&Switch to Frequency Now"), ui_->stations_table_view};
|
|
ui_->stations_table_view->insertAction(nullptr, station_hop_action_);
|
|
connect(station_hop_action_, &QAction::triggered, this,
|
|
&Configuration::impl::hop_to_station);
|
|
|
|
station_delete_action_ =
|
|
new QAction{tr("&Delete"), ui_->stations_table_view};
|
|
|
|
station_delete_action_->setShortcut(QKeySequence(Qt::Key_Delete));
|
|
station_delete_action_->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
|
|
|
ui_->stations_table_view->insertAction(nullptr, station_delete_action_);
|
|
connect(station_delete_action_, &QAction::triggered, this,
|
|
&Configuration::impl::delete_stations);
|
|
|
|
station_insert_action_ =
|
|
new QAction{tr("&Insert ..."), ui_->stations_table_view};
|
|
ui_->stations_table_view->insertAction(nullptr, station_insert_action_);
|
|
connect(station_insert_action_, &QAction::triggered, this,
|
|
&Configuration::impl::insert_station);
|
|
|
|
// Connect to selection changes and update action states
|
|
auto selection_model = ui_->stations_table_view->selectionModel();
|
|
connect(selection_model, &QItemSelectionModel::selectionChanged, this,
|
|
[this]() {
|
|
auto selected_rows =
|
|
ui_->stations_table_view->selectionModel()->selectedRows();
|
|
bool has_selection = !selected_rows.isEmpty();
|
|
bool has_single_selection = (selected_rows.size() == 1);
|
|
|
|
station_delete_action_->setVisible(has_selection);
|
|
station_hop_action_->setVisible(has_single_selection);
|
|
});
|
|
|
|
// Initial state
|
|
station_hop_action_->setVisible(false);
|
|
station_delete_action_->setVisible(false);
|
|
|
|
enumerate_rigs();
|
|
initialize_models();
|
|
|
|
audio_input_device_ = next_audio_input_device_;
|
|
audio_input_channel_ = next_audio_input_channel_;
|
|
audio_output_device_ = next_audio_output_device_;
|
|
audio_output_channel_ = next_audio_output_channel_;
|
|
notification_audio_output_device_ = next_notification_audio_output_device_;
|
|
|
|
transceiver_thread_ = new QThread{this};
|
|
transceiver_thread_->start();
|
|
}
|
|
|
|
Configuration::impl::~impl() {
|
|
transceiver_thread_->quit();
|
|
transceiver_thread_->wait();
|
|
write_settings();
|
|
}
|
|
|
|
void Configuration::impl::initialize_models() {
|
|
next_audio_input_device_ = audio_input_device_;
|
|
next_audio_input_channel_ = audio_input_channel_;
|
|
next_audio_output_device_ = audio_output_device_;
|
|
next_audio_output_channel_ = audio_output_channel_;
|
|
notification_audio_output_device_ = next_notification_audio_output_device_;
|
|
restart_sound_input_device_ = false;
|
|
restart_sound_output_device_ = false;
|
|
restart_notification_sound_output_device_ = false;
|
|
{
|
|
SettingsGroup g{settings_, "Configuration"};
|
|
find_audio_devices();
|
|
}
|
|
ui_->auto_switch_bands_check_box->setChecked(auto_switch_bands_);
|
|
ui_->callsign_line_edit->setText(my_callsign_);
|
|
ui_->grid_line_edit->setText(my_grid_.toUpper());
|
|
ui_->callsign_aging_spin_box->setValue(callsign_aging_);
|
|
ui_->activity_aging_spin_box->setValue(activity_aging_);
|
|
ui_->groups_line_edit->setText(my_groups_.join(", "));
|
|
ui_->auto_whitelist_line_edit->setText(auto_whitelist_.join(", "));
|
|
ui_->auto_blacklist_line_edit->setText(auto_blacklist_.join(", "));
|
|
ui_->hb_blacklist_line_edit->setText(hb_blacklist_.join(", "));
|
|
ui_->hb_rate_limit_check_box->setChecked(hb_rate_limit_);
|
|
ui_->spot_blacklist_line_edit->setText(spot_blacklist_.join(", "));
|
|
ui_->rx_blocklist_line_edit->setText(rx_callsign_blocklist_.join(", "));
|
|
ui_->primaryHighlightLineEdit->setText(primary_highlight_words_.join(", "));
|
|
ui_->secondaryHighlightLineEdit->setText(
|
|
secondary_highlight_words_.join(", "));
|
|
ui_->eot_line_edit->setText(eot_);
|
|
ui_->mfi_line_edit->setText(mfi_);
|
|
ui_->info_message_line_edit->setText(my_info_.toUpper());
|
|
ui_->status_message_line_edit->setText(my_status_.toUpper());
|
|
ui_->cq_message_line_edit->setText(
|
|
cq_.toUpper().replace("CQCQCQ", "CQ CQ CQ"));
|
|
ui_->hb_message_line_edit->setText(hb_.toUpper());
|
|
ui_->reply_message_line_edit->setText(reply_.toUpper());
|
|
ui_->use_dynamic_grid->setChecked(use_dynamic_info_);
|
|
|
|
ui_->tableForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_background_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->tableBackgroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_background_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->tableSelectionBackgroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->primaryHighlightLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_primary_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->secondaryHighlightLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_secondary_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->cqMessagesLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_cq_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->labMyCall->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_mycall_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
|
|
ui_->rxLabel->setStyleSheet(QString("background: %1; color: %2")
|
|
.arg(color_rx_background_.name())
|
|
.arg(color_rx_foreground_.name()));
|
|
ui_->rxForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(color_rx_background_.name())
|
|
.arg(color_rx_foreground_.name()));
|
|
ui_->txForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(color_rx_background_.name())
|
|
.arg(color_tx_foreground_.name()));
|
|
ui_->composeLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(color_compose_background_.name())
|
|
.arg(color_compose_foreground_.name()));
|
|
ui_->pills_enabled_check_box->setChecked(pills_enabled_);
|
|
updatePillPreview(ui_->pillRecipientLabel, next_color_pill_recipient_bg_,
|
|
next_color_pill_recipient_fg_);
|
|
updatePillPreview(ui_->pillCommandLabel, next_color_pill_command_bg_,
|
|
next_color_pill_command_fg_);
|
|
updatePillPreview(ui_->pillGroupLabel, next_color_pill_group_bg_,
|
|
next_color_pill_group_fg_);
|
|
updatePillPreview(ui_->pillSenderLabel, next_color_pill_sender_bg_,
|
|
next_color_pill_sender_fg_);
|
|
|
|
ui_->sbTxDelay->setValue(txDelay_);
|
|
ui_->PTT_method_button_group->button(rig_params_.ptt_type)
|
|
->setChecked(true);
|
|
ui_->save_path_display_label->setText(save_directory_.absolutePath());
|
|
ui_->write_logs_check_box->setChecked(write_logs_);
|
|
ui_->reset_activity_check_box->setChecked(reset_activity_);
|
|
ui_->checkForUpdates_checkBox->setChecked(check_for_updates_);
|
|
ui_->tx_qsy_check_box->setChecked(tx_qsy_allowed_);
|
|
ui_->psk_reporter_check_box->setChecked(spot_to_reporting_networks_);
|
|
ui_->enable_aprs_spotting_check_box->setChecked(spot_to_aprs_);
|
|
ui_->enable_aprs_relay_check_box->setChecked(spot_to_aprs_relay_);
|
|
ui_->transmit_directed_check_box->setChecked(transmit_directed_);
|
|
ui_->autoreply_on_check_box->setChecked(autoreply_on_at_startup_);
|
|
ui_->autoreply_confirmation_check_box->setChecked(autoreply_confirmation_);
|
|
ui_->heartbeat_anywhere_check_box->setChecked(heartbeat_anywhere_);
|
|
ui_->heartbeat_qso_pause_check_box->setChecked(heartbeat_qso_pause_);
|
|
ui_->heartbeat_ack_snr_check_box->setChecked(heartbeat_ack_snr_);
|
|
ui_->relay_disabled_check_box->setChecked(relay_disabled_);
|
|
ui_->monitor_off_check_box->setChecked(monitor_off_at_startup_);
|
|
ui_->transmit_off_check_box->setChecked(transmit_off_at_startup_);
|
|
ui_->monitor_last_used_check_box->setChecked(monitor_last_used_);
|
|
ui_->stations_table_view->setEnabled(
|
|
ui_->auto_switch_bands_check_box->isChecked());
|
|
ui_->miles_check_box->setChecked(miles_);
|
|
ui_->hold_ptt_check_box->setChecked(hold_ptt_);
|
|
ui_->avoid_forced_identify_check_box->setChecked(avoid_forced_identify_);
|
|
ui_->avoid_allcall_check_box->setChecked(avoid_allcall_);
|
|
ui_->spellcheck_check_box->setChecked(spellcheck_);
|
|
ui_->heartbeat_spin_box->setValue(heartbeat_);
|
|
ui_->tx_watchdog_spin_box->setValue(watchdog_);
|
|
ui_->rig_combo_box->setCurrentText(rig_params_.rig_name);
|
|
ui_->TX_mode_button_group->button(data_mode_)->setChecked(true);
|
|
ui_->split_mode_button_group->button(rig_params_.split_mode)
|
|
->setChecked(true);
|
|
ui_->CAT_serial_baud_combo_box->setCurrentText(
|
|
QString::number(rig_params_.baud));
|
|
ui_->CAT_data_bits_button_group->button(rig_params_.data_bits)
|
|
->setChecked(true);
|
|
ui_->CAT_stop_bits_button_group->button(rig_params_.stop_bits)
|
|
->setChecked(true);
|
|
ui_->CAT_handshake_button_group->button(rig_params_.handshake)
|
|
->setChecked(true);
|
|
ui_->checkBoxPwrBandTxMemory->setChecked(pwrBandTxMemory_);
|
|
ui_->checkBoxPwrBandTuneMemory->setChecked(pwrBandTuneMemory_);
|
|
if (rig_params_.force_dtr) {
|
|
ui_->force_DTR_combo_box->setCurrentIndex(rig_params_.dtr_high ? 1 : 2);
|
|
} else {
|
|
ui_->force_DTR_combo_box->setCurrentIndex(0);
|
|
}
|
|
if (rig_params_.force_rts) {
|
|
ui_->force_RTS_combo_box->setCurrentIndex(rig_params_.rts_high ? 1 : 2);
|
|
} else {
|
|
ui_->force_RTS_combo_box->setCurrentIndex(0);
|
|
}
|
|
ui_->TX_audio_source_button_group->button(rig_params_.audio_source)
|
|
->setChecked(true);
|
|
ui_->CAT_poll_interval_spin_box->setValue(rig_params_.poll_interval);
|
|
ui_->opCallEntry->setText(opCall_);
|
|
ui_->ptt_command_line_edit->setText(ptt_command_);
|
|
ui_->aprs_server_line_edit->setText(aprs_server_name_);
|
|
ui_->aprs_server_port_spin_box->setValue(aprs_server_port_);
|
|
ui_->udp_server_line_edit->setText(udp_server_name_);
|
|
ui_->udp_server_port_spin_box->setValue(udp_server_port_);
|
|
ui_->tcp_server_line_edit->setText(tcp_server_name_);
|
|
ui_->tcp_server_port_spin_box->setValue(tcp_server_port_);
|
|
ui_->accept_udp_requests_check_box->setChecked(accept_udp_requests_);
|
|
ui_->accept_tcp_requests_check_box->setChecked(accept_tcp_requests_);
|
|
ui_->n3fjp_server_name_line_edit->setText(n3fjp_server_name_);
|
|
ui_->n3fjp_server_port_spin_box->setValue(n3fjp_server_port_);
|
|
ui_->enable_n3fjp_broadcast_check_box->setChecked(broadcast_to_n3fjp_);
|
|
ui_->n1mm_server_name_line_edit->setText(n1mm_server_name_);
|
|
ui_->n1mm_server_port_spin_box->setValue(n1mm_server_port_);
|
|
ui_->enable_n1mm_broadcast_check_box->setChecked(broadcast_to_n1mm_);
|
|
ui_->tcpEnable->setChecked(tcpEnabled_);
|
|
ui_->udpEnable->setChecked(udpEnabled_);
|
|
ui_->tcp_max_connections_spin_box->setValue(tcpMaxConnections_);
|
|
// WSJT-X Protocol settings
|
|
ui_->wsjtx_enable_check_box->setChecked(wsjtx_protocol_enabled_);
|
|
ui_->wsjtx_server_line_edit->setText(wsjtx_server_name_);
|
|
ui_->wsjtx_server_port_spin_box->setValue(wsjtx_server_port_);
|
|
ui_->wsjtx_TTL_spin_box->setValue(wsjtx_TTL_);
|
|
ui_->wsjtx_accept_requests_check_box->setChecked(wsjtx_accept_requests_);
|
|
load_network_interfaces(ui_->wsjtx_interfaces_combo_box,
|
|
wsjtx_interface_names_);
|
|
ui_->calibration_intercept_spin_box->setValue(calibration_.intercept);
|
|
ui_->calibration_slope_ppm_spin_box->setValue(calibration_.slope_ppm);
|
|
|
|
if (rig_params_.ptt_port.isEmpty()) {
|
|
if (ui_->PTT_port_combo_box->count()) {
|
|
ui_->PTT_port_combo_box->setCurrentText(
|
|
ui_->PTT_port_combo_box->itemText(0));
|
|
}
|
|
} else {
|
|
ui_->PTT_port_combo_box->setCurrentText(rig_params_.ptt_port);
|
|
}
|
|
|
|
ui_->region_combo_box->setCurrentIndex(region_);
|
|
|
|
next_macros_.setStringList(macros_.stringList());
|
|
next_frequencies_.frequency_list(frequencies_.frequency_list());
|
|
next_stations_.station_list(stations_.station_list());
|
|
|
|
//
|
|
// setup notifications table view
|
|
//
|
|
ui_->notifications_check_box->setChecked(enable_notifications_);
|
|
on_notifications_check_box_toggled(enable_notifications_);
|
|
|
|
QList<QPair<QString, QString>> notifyRows = {
|
|
{"cq", "CQ Message Received"},
|
|
{"hb", "HB Message Received"},
|
|
{"ack", "ACK Message Received"},
|
|
{"directed", "Directed Message Received"},
|
|
{"inbox", "Inbox Message Received"},
|
|
{"call_new", "New Callsign Heard"},
|
|
{"call_old", "Worked Callsign Heard"},
|
|
};
|
|
|
|
int i = 0;
|
|
auto table = ui_->notifications_table_widget;
|
|
for (int i = ui_->notifications_table_widget->rowCount() - 1; i >= 0; i--) {
|
|
ui_->notifications_table_widget->removeRow(i);
|
|
}
|
|
auto header = table->horizontalHeader();
|
|
header->setStretchLastSection(false);
|
|
header->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
|
header->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
|
header->setSectionResizeMode(2, QHeaderView::Stretch);
|
|
header->setSectionResizeMode(3, QHeaderView::ResizeToContents);
|
|
foreach (auto pair, notifyRows) {
|
|
QString key = pair.first;
|
|
QString value = pair.second;
|
|
bool enabled = notifications_enabled_.value(key, false);
|
|
QString path = notifications_paths_.value(key, "");
|
|
|
|
QCheckBox *enabledCheckbox;
|
|
auto enabledWidget = centeredCheckBox(this, &enabledCheckbox);
|
|
enabledWidget->setMinimumWidth(100);
|
|
if (enabledCheckbox) {
|
|
enabledCheckbox->setObjectName("enabledCheckbox");
|
|
enabledCheckbox->setChecked(enabled);
|
|
}
|
|
|
|
auto expandingPolicy =
|
|
QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
expandingPolicy.setHorizontalStretch(1);
|
|
|
|
QLabel *pathLabel = new QLabel(this);
|
|
pathLabel->setText(path);
|
|
pathLabel->setSizePolicy(expandingPolicy);
|
|
|
|
auto minimumPolicy =
|
|
QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
|
minimumPolicy.setHorizontalStretch(0);
|
|
|
|
QWidget *buttonWidget = new QWidget(this);
|
|
buttonWidget->setSizePolicy(minimumPolicy);
|
|
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget);
|
|
buttonLayout->setStretch(0, 0);
|
|
buttonLayout->setStretch(1, 0);
|
|
buttonLayout->setSpacing(3);
|
|
buttonLayout->setContentsMargins(3, 1, 3, 1);
|
|
buttonWidget->setLayout(buttonLayout);
|
|
|
|
QPushButton *selectFilePushButton = new QPushButton("Select", this);
|
|
selectFilePushButton->setSizePolicy(minimumPolicy);
|
|
buttonLayout->addWidget(selectFilePushButton);
|
|
|
|
connect(selectFilePushButton, &QPushButton::pressed, this,
|
|
[this, pathLabel]() {
|
|
auto dir = QStandardPaths::standardLocations(
|
|
QStandardPaths::MusicLocation);
|
|
auto path = QFileDialog::getOpenFileName(
|
|
this, "Audio Notification", dir.first(),
|
|
"Wave Files (*.wav);;All files (*.*)");
|
|
if (!path.isEmpty()) {
|
|
pathLabel->setText(path);
|
|
}
|
|
});
|
|
|
|
QPushButton *testPushButton = new QPushButton("Test", this);
|
|
testPushButton->setSizePolicy(minimumPolicy);
|
|
buttonLayout->addWidget(testPushButton);
|
|
|
|
connect(testPushButton, &QPushButton::pressed, this,
|
|
[this, key]() {
|
|
emit this->self_->test_notify(key);
|
|
});
|
|
|
|
QPushButton *clearPushButton = new QPushButton("Clear", this);
|
|
clearPushButton->setSizePolicy(minimumPolicy);
|
|
buttonLayout->addWidget(clearPushButton);
|
|
|
|
connect(clearPushButton, &QPushButton::pressed, this,
|
|
[pathLabel, enabledCheckbox]() {
|
|
pathLabel->clear();
|
|
enabledCheckbox->setChecked(false);
|
|
});
|
|
|
|
// row config
|
|
int col = 0;
|
|
table->insertRow(i);
|
|
|
|
//
|
|
auto eventLabelItem = new QTableWidgetItem(value);
|
|
eventLabelItem->setData(Qt::UserRole, QVariant(key));
|
|
table->setItem(i, col++, eventLabelItem);
|
|
|
|
table->setCellWidget(i, col++, enabledWidget);
|
|
|
|
table->setCellWidget(i, col++, pathLabel);
|
|
|
|
table->setCellWidget(i, col++, buttonWidget);
|
|
i++;
|
|
}
|
|
|
|
table->resizeRowsToContents();
|
|
table->resizeColumnsToContents();
|
|
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::done(int r) {
|
|
// do this here since window is still on screen at this point
|
|
SettingsGroup g{settings_, "Configuration"};
|
|
settings_->setValue("WindowGeometry", saveGeometry());
|
|
|
|
QDialog::done(r);
|
|
}
|
|
|
|
void Configuration::impl::read_settings() {
|
|
SettingsGroup g{settings_, "Configuration"};
|
|
setMinimumSize(800, 400);
|
|
restoreGeometry(settings_->value("WindowGeometry").toByteArray());
|
|
setMinimumSize(800, 400);
|
|
|
|
auto_switch_bands_ = settings_->value("AutoSwitchBands", false).toBool();
|
|
my_callsign_ = settings_->value("MyCall", QString{}).toString();
|
|
my_grid_ = settings_->value("MyGrid", QString{}).toString();
|
|
my_groups_ = settings_->value("MyGroups", QStringList{}).toStringList();
|
|
auto_whitelist_ =
|
|
settings_->value("AutoWhitelist", QStringList{}).toStringList();
|
|
auto_blacklist_ =
|
|
settings_->value("AutoBlacklist", QStringList{}).toStringList();
|
|
hb_blacklist_ =
|
|
settings_->value("HBBlacklist", QStringList{}).toStringList();
|
|
hb_rate_limit_ = settings_->value("HBRateLimit", false).toBool();
|
|
spot_blacklist_ =
|
|
settings_->value("SpotBlacklist", QStringList{}).toStringList();
|
|
rx_callsign_blocklist_ =
|
|
settings_->value("RXCallsignBlocklist", QStringList{}).toStringList();
|
|
primary_highlight_words_ =
|
|
settings_->value("PrimaryHighlightWords", QStringList{}).toStringList();
|
|
secondary_highlight_words_ =
|
|
settings_->value("SecondaryHighlightWords", QStringList{})
|
|
.toStringList();
|
|
callsign_aging_ = settings_->value("CallsignAging", 0).toInt();
|
|
activity_aging_ = settings_->value("ActivityAging", 2).toInt();
|
|
eot_ = settings_->value("EOTCharacter", QString{"\u2662"}).toString();
|
|
mfi_ = settings_->value("MFICharacter", QString{"\u2026\u2026"}).toString();
|
|
my_info_ = settings_->value("MyInfo", QString{}).toString();
|
|
my_status_ =
|
|
settings_
|
|
->value("MyStatus", QString{"IDLE <MYIDLE> VERSION <MYVERSION>"})
|
|
.toString();
|
|
hb_ = settings_->value("HBMessage", QString{"HB <MYGRID4>"}).toString();
|
|
cq_ =
|
|
settings_->value("CQMessage", QString{"CQ CQ CQ <MYGRID4>"}).toString();
|
|
reply_ = settings_->value("Reply", QString{"HW CPY?"}).toString();
|
|
next_color_cq_ = color_cq_ =
|
|
settings_->value("colorCQ", "#66ff66").toString();
|
|
next_color_primary_highlight_ = color_primary_highlight_ =
|
|
settings_->value("colorPrimary", "#f1c40f").toString();
|
|
next_color_secondary_highlight_ = color_secondary_highlight_ =
|
|
settings_->value("colorSecondary", "#ffff66").toString();
|
|
next_color_mycall_ = color_mycall_ =
|
|
settings_->value("colorMyCall", "#ff6666").toString();
|
|
next_color_rx_background_ = color_rx_background_ =
|
|
settings_->value("color_rx_background", "#ffeaa7").toString();
|
|
next_color_rx_foreground_ = color_rx_foreground_ =
|
|
settings_->value("color_rx_foreground", "#000000").toString();
|
|
next_color_compose_background_ = color_compose_background_ =
|
|
settings_->value("color_compose_background", "#ffffff").toString();
|
|
next_color_compose_foreground_ = color_compose_foreground_ =
|
|
settings_->value("color_compose_foreground", "#000000").toString();
|
|
next_color_tx_foreground_ = color_tx_foreground_ =
|
|
settings_->value("color_tx_foreground", "#ff0000").toString();
|
|
next_color_DXCC_ = color_DXCC_ =
|
|
settings_->value("colorDXCC", "#ff00ff").toString();
|
|
next_color_NewCall_ = color_NewCall_ =
|
|
settings_->value("colorNewCall", "#ffaaff").toString();
|
|
|
|
next_color_table_background_ = color_table_background_ =
|
|
settings_->value("colorTableBackground", "#ffffff").toString();
|
|
next_color_table_highlight_ = color_table_highlight_ =
|
|
settings_->value("colorTableHighlight", "#3498db").toString();
|
|
next_color_table_foreground_ = color_table_foreground_ =
|
|
settings_->value("colorTableForeground", "#000000").toString();
|
|
|
|
pills_enabled_ = settings_->value("PillsEnabled", true).toBool();
|
|
|
|
next_color_pill_recipient_bg_ = color_pill_recipient_bg_ =
|
|
settings_->value("colorPillRecipientBg", "#2980b9").toString();
|
|
next_color_pill_recipient_fg_ = color_pill_recipient_fg_ =
|
|
settings_->value("colorPillRecipientFg", "#ffffff").toString();
|
|
next_color_pill_command_bg_ = color_pill_command_bg_ =
|
|
settings_->value("colorPillCommandBg", "#e67e22").toString();
|
|
next_color_pill_command_fg_ = color_pill_command_fg_ =
|
|
settings_->value("colorPillCommandFg", "#ffffff").toString();
|
|
next_color_pill_group_bg_ = color_pill_group_bg_ =
|
|
settings_->value("colorPillGroupBg", "#16a085").toString();
|
|
next_color_pill_group_fg_ = color_pill_group_fg_ =
|
|
settings_->value("colorPillGroupFg", "#ffffff").toString();
|
|
next_color_pill_sender_bg_ = color_pill_sender_bg_ =
|
|
settings_->value("colorPillSenderBg", "#7f8c8d").toString();
|
|
next_color_pill_sender_fg_ = color_pill_sender_fg_ =
|
|
settings_->value("colorPillSenderFg", "#ffffff").toString();
|
|
|
|
if (next_font_.fromString(
|
|
settings_->value("Font", QGuiApplication::font().toString())
|
|
.toString()) &&
|
|
next_font_ != font_) {
|
|
font_ = next_font_;
|
|
Q_EMIT self_->gui_text_font_changed(font_);
|
|
} else {
|
|
next_font_ = font_;
|
|
}
|
|
ui_->font_push_button->setText(QString("Application Font (%1 %2)")
|
|
.arg(next_font_.family())
|
|
.arg(next_font_.pointSize()));
|
|
|
|
if (next_tx_text_font_.fromString(
|
|
settings_->value("TXTextFont", QGuiApplication::font().toString())
|
|
.toString()) &&
|
|
next_tx_text_font_ != tx_text_font_) {
|
|
tx_text_font_ = next_tx_text_font_;
|
|
Q_EMIT self_->tx_text_font_changed(tx_text_font_);
|
|
} else {
|
|
next_tx_text_font_ = tx_text_font_;
|
|
}
|
|
|
|
ui_->txFontButton->setText(QString("Font (%1 %2)")
|
|
.arg(next_tx_text_font_.family())
|
|
.arg(next_tx_text_font_.pointSize()));
|
|
|
|
if (next_rx_text_font_.fromString(
|
|
settings_->value("RXTextFont", QGuiApplication::font().toString())
|
|
.toString()) &&
|
|
next_rx_text_font_ != rx_text_font_) {
|
|
rx_text_font_ = next_rx_text_font_;
|
|
Q_EMIT self_->rx_text_font_changed(rx_text_font_);
|
|
} else {
|
|
next_rx_text_font_ = rx_text_font_;
|
|
}
|
|
|
|
ui_->rxFontButton->setText(QString("Font (%1 %2)")
|
|
.arg(next_rx_text_font_.family())
|
|
.arg(next_rx_text_font_.pointSize()));
|
|
|
|
if (next_compose_text_font_.fromString(
|
|
settings_
|
|
->value("composeTextFont", QGuiApplication::font().toString())
|
|
.toString()) &&
|
|
next_compose_text_font_ != compose_text_font_) {
|
|
compose_text_font_ = next_compose_text_font_;
|
|
Q_EMIT self_->compose_text_font_changed(compose_text_font_);
|
|
} else {
|
|
next_compose_text_font_ = compose_text_font_;
|
|
}
|
|
ui_->composeFontButton->setText(
|
|
QString("Font (%1 %2)")
|
|
.arg(next_compose_text_font_.family())
|
|
.arg(next_compose_text_font_.pointSize()));
|
|
|
|
if (next_table_font_.fromString(
|
|
settings_->value("tableFont", QGuiApplication::font().toString())
|
|
.toString()) &&
|
|
next_table_font_ != table_font_) {
|
|
table_font_ = next_table_font_;
|
|
Q_EMIT self_->table_font_changed(table_font_);
|
|
} else {
|
|
next_table_font_ = table_font_;
|
|
}
|
|
ui_->tableFontButton->setText(QString("Font (%1 %2)")
|
|
.arg(next_table_font_.family())
|
|
.arg(next_table_font_.pointSize()));
|
|
|
|
txDelay_ = settings_->value("TxDelay", 0.2).toDouble();
|
|
save_directory_.setPath(
|
|
settings_->value("SaveDir", default_save_directory_.absolutePath())
|
|
.toString());
|
|
|
|
// retrieve audio channel info
|
|
audio_input_channel_ = AudioDevice::fromString(
|
|
settings_->value("AudioInputChannel", "Mono").toString());
|
|
audio_output_channel_ = AudioDevice::fromString(
|
|
settings_->value("AudioOutputChannel", "Mono").toString());
|
|
|
|
transmit_directed_ = settings_->value("TransmitDirected", true).toBool();
|
|
autoreply_on_at_startup_ =
|
|
settings_->value("AutoreplyOnAtStartup", true).toBool();
|
|
autoreply_confirmation_ =
|
|
settings_->value("AutoreplyConfirmation", true).toBool();
|
|
heartbeat_anywhere_ = settings_->value("BeaconAnywhere", false).toBool();
|
|
heartbeat_qso_pause_ = settings_->value("HeartbeatQSOPause", true).toBool();
|
|
heartbeat_ack_snr_ = settings_->value("HeartbeatAckSNR", false).toBool();
|
|
relay_disabled_ = settings_->value("RelayOFF", false).toBool();
|
|
monitor_off_at_startup_ = settings_->value("MonitorOFF", false).toBool();
|
|
transmit_off_at_startup_ = settings_->value("TransmitOFF", false).toBool();
|
|
monitor_last_used_ = settings_->value("MonitorLastUsed", false).toBool();
|
|
spot_to_reporting_networks_ =
|
|
settings_->value("PSKReporter", true).toBool();
|
|
spot_to_aprs_ = settings_->value("SpotToAPRS", true).toBool();
|
|
spot_to_aprs_relay_ = settings_->value("APRSISRelay", false).toBool();
|
|
write_logs_ = settings_->value("WriteLogs", true).toBool();
|
|
reset_activity_ = settings_->value("ResetActivity", false).toBool();
|
|
check_for_updates_ = settings_->value("CheckForUpdates", true).toBool();
|
|
tx_qsy_allowed_ = settings_->value("TxQSYAllowed", false).toBool();
|
|
use_dynamic_info_ = settings_->value("AutoGrid", false).toBool();
|
|
|
|
auto loadedMacros =
|
|
settings_->value("Macros", QStringList{"TNX 73 GL"}).toStringList();
|
|
|
|
macros_.setStringList(loadedMacros);
|
|
|
|
region_ = settings_->value("Region", QVariant::fromValue(IARURegions::ALL))
|
|
.value<IARURegions::Region>();
|
|
|
|
if (settings_->contains(versionedFrequenciesSettingsKey)) {
|
|
auto const &v = settings_->value(versionedFrequenciesSettingsKey);
|
|
if (v.isValid()) {
|
|
frequencies_.frequency_list(
|
|
v.value<FrequencyList_v3::FrequencyItems>());
|
|
} else {
|
|
frequencies_.reset_to_defaults();
|
|
}
|
|
} else // No new key: try to migrate from old key
|
|
if (settings_->contains(v2FrequenciesSettingsKey)) {
|
|
const QVariant v = settings_->value(v2FrequenciesSettingsKey);
|
|
if (v.isValid()) {
|
|
auto oldItems = v.value<FrequencyList_v2::FrequencyItems>();
|
|
|
|
FrequencyList_v3::FrequencyItems newItems;
|
|
newItems.reserve(oldItems.size());
|
|
for (const auto& it : oldItems) {
|
|
FrequencyList_v3::Item n;
|
|
n.frequency_ = it.frequency_;
|
|
n.mode_ = it.mode_;
|
|
n.region_ = it.region_;
|
|
n.description_.clear();
|
|
newItems.push_back(std::move(n));
|
|
}
|
|
|
|
frequencies_.frequency_list(std::move(newItems));
|
|
|
|
// Persist under new key
|
|
settings_->setValue(versionedFrequenciesSettingsKey, QVariant::fromValue(frequencies_.frequency_list()));
|
|
|
|
// Optionally remove the old version
|
|
//settings_->remove(v2FrequenciesSettingsKey);
|
|
write_settings();
|
|
}
|
|
} else {
|
|
frequencies_.reset_to_defaults();
|
|
}
|
|
|
|
stations_.station_list(
|
|
settings_->value("stations").value<StationList::Stations>());
|
|
|
|
rig_params_.rig_name =
|
|
settings_->value("Rig", TransceiverFactory::basic_transceiver_name_)
|
|
.toString();
|
|
rig_is_dummy_ =
|
|
TransceiverFactory::basic_transceiver_name_ == rig_params_.rig_name;
|
|
rig_params_.network_port = settings_->value("CATNetworkPort").toString();
|
|
rig_params_.usb_port = settings_->value("CATUSBPort").toString();
|
|
rig_params_.serial_port = settings_->value("CATSerialPort").toString();
|
|
rig_params_.baud = settings_->value("CATSerialRate", 4800).toInt();
|
|
rig_params_.data_bits =
|
|
settings_
|
|
->value("CATDataBits",
|
|
QVariant::fromValue(TransceiverFactory::default_data_bits))
|
|
.value<TransceiverFactory::DataBits>();
|
|
rig_params_.stop_bits =
|
|
settings_
|
|
->value("CATStopBits",
|
|
QVariant::fromValue(TransceiverFactory::default_stop_bits))
|
|
.value<TransceiverFactory::StopBits>();
|
|
rig_params_.handshake =
|
|
settings_
|
|
->value("CATHandshake",
|
|
QVariant::fromValue(TransceiverFactory::handshake_default))
|
|
.value<TransceiverFactory::Handshake>();
|
|
rig_params_.force_dtr = settings_->value("CATForceDTR", false).toBool();
|
|
rig_params_.dtr_high = settings_->value("DTR", false).toBool();
|
|
rig_params_.force_rts = settings_->value("CATForceRTS", false).toBool();
|
|
rig_params_.rts_high = settings_->value("RTS", false).toBool();
|
|
rig_params_.ptt_type =
|
|
settings_
|
|
->value("PTTMethod",
|
|
QVariant::fromValue(TransceiverFactory::PTT_method_VOX))
|
|
.value<TransceiverFactory::PTTMethod>();
|
|
rig_params_.audio_source =
|
|
settings_
|
|
->value(
|
|
"TXAudioSource",
|
|
QVariant::fromValue(TransceiverFactory::TX_audio_source_front))
|
|
.value<TransceiverFactory::TXAudioSource>();
|
|
rig_params_.ptt_port = settings_->value("PTTport").toString();
|
|
data_mode_ =
|
|
settings_->value("DataMode", QVariant::fromValue(data_mode_none))
|
|
.value<Configuration::DataMode>();
|
|
insert_blank_ = settings_->value("InsertBlank", false).toBool();
|
|
DXCC_ = settings_->value("DXCCEntity", false).toBool();
|
|
ppfx_ = settings_->value("PrincipalPrefix", false).toBool();
|
|
miles_ = settings_->value("Miles", false).toBool();
|
|
hold_ptt_ = settings_->value("HoldPTT", false).toBool();
|
|
avoid_forced_identify_ =
|
|
settings_->value("AvoidForcedIdentify", false).toBool();
|
|
avoid_allcall_ = settings_->value("AvoidAllcall", false).toBool();
|
|
spellcheck_ = settings_->value("Spellcheck", true).toBool();
|
|
heartbeat_ = settings_->value("TxBeacon", 30).toInt();
|
|
watchdog_ = settings_->value("TxIdleWatchdog", 60).toInt();
|
|
if (watchdog_) {
|
|
watchdog_ = qMax(5, watchdog_);
|
|
}
|
|
TX_messages_ = settings_->value("Tx2QSO", true).toBool();
|
|
rig_params_.poll_interval = settings_->value("Polling", 0).toInt();
|
|
rig_params_.split_mode =
|
|
settings_
|
|
->value("SplitMode",
|
|
QVariant::fromValue(TransceiverFactory::split_mode_none))
|
|
.value<TransceiverFactory::SplitMode>();
|
|
opCall_ = settings_->value("OpCall", "").toString();
|
|
ptt_command_ = settings_->value("PTTCommand", "").toString();
|
|
aprs_server_name_ =
|
|
settings_->value("aprsServer", "rotate.aprs2.net").toString();
|
|
aprs_server_port_ = settings_->value("aprsServerPort", 14580).toUInt();
|
|
udp_server_name_ = settings_->value("UDPServer", "127.0.0.1").toString();
|
|
udp_server_port_ = settings_->value("UDPServerPort", 2242).toUInt();
|
|
// WSJT-X Protocol settings
|
|
wsjtx_protocol_enabled_ =
|
|
settings_->value("WSJTXProtocolEnabled", false).toBool();
|
|
wsjtx_server_name_ =
|
|
settings_->value("WSJTXServer", "127.0.0.1").toString();
|
|
wsjtx_server_port_ = settings_->value("WSJTXServerPort", 2237).toUInt();
|
|
wsjtx_TTL_ = settings_->value("WSJTXTTL", 1).toInt();
|
|
wsjtx_accept_requests_ =
|
|
settings_->value("WSJTXAcceptRequests", false).toBool();
|
|
wsjtx_interface_names_ =
|
|
settings_->value("WSJTXInterfaces", QStringList()).toStringList();
|
|
tcp_server_name_ = settings_->value("TCPServer", "127.0.0.1").toString();
|
|
tcp_server_port_ = settings_->value("TCPServerPort", 2442).toUInt();
|
|
n3fjp_server_name_ =
|
|
settings_->value("N3FJPServer", "127.0.0.1").toString();
|
|
n3fjp_server_port_ = settings_->value("N3FJPServerPort", 1100).toUInt();
|
|
broadcast_to_n3fjp_ = settings_->value("BroadcastToN3FJP", false).toBool();
|
|
n1mm_server_name_ = settings_->value("N1MMServer", "127.0.0.1").toString();
|
|
n1mm_server_port_ = settings_->value("N1MMServerPort", 2333).toUInt();
|
|
broadcast_to_n1mm_ = settings_->value("BroadcastToN1MM", false).toBool();
|
|
accept_udp_requests_ =
|
|
settings_->value("AcceptUDPRequests", false).toBool();
|
|
accept_tcp_requests_ =
|
|
settings_->value("AcceptTCPRequests", false).toBool();
|
|
udpEnabled_ = settings_->value("UDPEnabled", false).toBool();
|
|
tcpEnabled_ = settings_->value("TCPEnabled", false).toBool();
|
|
tcpMaxConnections_ = settings_->value("TCPMaxConnections", 1).toInt();
|
|
calibration_.intercept =
|
|
settings_->value("CalibrationIntercept", 0.).toDouble();
|
|
calibration_.slope_ppm =
|
|
settings_->value("CalibrationSlopePPM", 0.).toDouble();
|
|
pwrBandTxMemory_ = settings_->value("pwrBandTxMemory", false).toBool();
|
|
pwrBandTuneMemory_ = settings_->value("pwrBandTuneMemory", false).toBool();
|
|
|
|
// notifications
|
|
enable_notifications_ =
|
|
settings_->value("EnableNotifications", false).toBool();
|
|
notifications_enabled_.clear();
|
|
notifications_paths_.clear();
|
|
settings_->beginGroup("Notifications");
|
|
{
|
|
foreach (auto group, settings_->childGroups()) {
|
|
settings_->beginGroup(group);
|
|
{
|
|
notifications_enabled_[group] =
|
|
settings_->value("enabled", QVariant(false)).toBool();
|
|
notifications_paths_[group] =
|
|
settings_->value("path", QVariant("")).toString();
|
|
}
|
|
settings_->endGroup();
|
|
}
|
|
}
|
|
settings_->endGroup();
|
|
}
|
|
|
|
void Configuration::impl::find_audio_devices()
|
|
{
|
|
// Default true: let's give users access to all available inputs
|
|
// Later we can add a checkbox to the UI to toggle this if it bothers people
|
|
const bool allow_monitor_inputs =
|
|
settings_->value("AllowMonitorInputs", false).toBool();
|
|
|
|
// INPUT
|
|
{
|
|
AudioBinding b;
|
|
b.mode = QAudioDevice::Input;
|
|
b.device_combo = ui_->sound_input_combo_box;
|
|
b.channel_combo = ui_->sound_input_channel_combo_box;
|
|
b.allow_both = false;
|
|
b.allow_monitor_inputs = allow_monitor_inputs;
|
|
b.key_id = "SoundInId";
|
|
b.key_name = "SoundInName";
|
|
b.key_channel = "AudioInputChannel";
|
|
|
|
resolve_audio_binding(b, next_audio_input_device_, &next_audio_input_channel_);
|
|
}
|
|
|
|
// OUTPUT
|
|
{
|
|
AudioBinding b;
|
|
b.mode = QAudioDevice::Output;
|
|
b.device_combo = ui_->sound_output_combo_box;
|
|
b.channel_combo = ui_->sound_output_channel_combo_box;
|
|
b.allow_both = true;
|
|
b.allow_monitor_inputs = true; // irrelevant for outputs
|
|
b.key_id = "SoundOutId";
|
|
b.key_name = "SoundOutName";
|
|
b.key_channel = "AudioOutputChannel";
|
|
|
|
resolve_audio_binding(b, next_audio_output_device_, &next_audio_output_channel_);
|
|
}
|
|
|
|
// NOTIFICATION OUTPUT
|
|
{
|
|
AudioBinding b;
|
|
b.mode = QAudioDevice::Output;
|
|
b.device_combo = ui_->notification_sound_output_combo_box;
|
|
b.channel_combo = nullptr;
|
|
b.allow_both = false;
|
|
b.allow_monitor_inputs = true;
|
|
b.key_id = "NotificationSoundOutId";
|
|
b.key_name = "NotificationSoundOutName";
|
|
b.key_channel = nullptr;
|
|
|
|
resolve_audio_binding(b, next_notification_audio_output_device_, nullptr);
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::resolve_audio_binding(AudioBinding const& b,
|
|
QAudioDevice& next_device,
|
|
AudioDevice::Channel* next_channel)
|
|
{
|
|
auto set_if_changed = [&](char const* key, QVariant const& value) {
|
|
if (settings_->value(key) != value)
|
|
settings_->setValue(key, value);
|
|
};
|
|
|
|
auto ensure_valid_index = [](QComboBox* cb) {
|
|
if (!cb) return -1;
|
|
int idx = cb->currentIndex();
|
|
if (idx < 0 && cb->count() > 0) {
|
|
cb->setCurrentIndex(0);
|
|
idx = 0;
|
|
}
|
|
return idx;
|
|
};
|
|
|
|
const QString saved_id = b.key_id ? settings_->value(b.key_id).toString() : QString{};
|
|
const QString saved_name = b.key_name ? settings_->value(b.key_name).toString() : QString{};
|
|
|
|
const bool need_resolve =
|
|
(!saved_id.isEmpty() && dev_id(next_device) != saved_id) ||
|
|
(saved_id.isEmpty() && next_device.isNull());
|
|
|
|
if (need_resolve) {
|
|
next_device = find_audio_device(b.mode,
|
|
b.device_combo,
|
|
saved_id,
|
|
saved_name,
|
|
b.allow_monitor_inputs);
|
|
|
|
if (!next_device.isNull() && b.key_id)
|
|
set_if_changed(b.key_id, next_device.id());
|
|
}
|
|
|
|
if (b.key_channel && b.channel_combo && next_channel) {
|
|
*next_channel = AudioDevice::fromString(
|
|
settings_->value(b.key_channel, "Mono").toString());
|
|
|
|
const int idx = ensure_valid_index(b.device_combo);
|
|
if (idx >= 0) {
|
|
update_audio_channels(b.device_combo,
|
|
b.channel_combo,
|
|
idx,
|
|
b.allow_both);
|
|
}
|
|
b.channel_combo->setCurrentIndex(static_cast<int>(*next_channel));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::write_settings() {
|
|
SettingsGroup g{settings_, "Configuration"};
|
|
|
|
settings_->setValue("AutoSwitchBands", auto_switch_bands_);
|
|
settings_->setValue("MyCall", my_callsign_);
|
|
settings_->setValue("MyGrid", my_grid_);
|
|
settings_->setValue("MyGroups", my_groups_);
|
|
settings_->setValue("AutoWhitelist", auto_whitelist_);
|
|
settings_->setValue("AutoBlacklist", auto_blacklist_);
|
|
settings_->setValue("HBBlacklist", hb_blacklist_);
|
|
settings_->setValue("HBRateLimit", hb_rate_limit_);
|
|
settings_->setValue("SpotBlacklist", spot_blacklist_);
|
|
settings_->setValue("RXCallsignBlocklist", rx_callsign_blocklist_);
|
|
settings_->setValue("PrimaryHighlightWords", primary_highlight_words_);
|
|
settings_->setValue("SecondaryHighlightWords", secondary_highlight_words_);
|
|
settings_->setValue("EOTCharacter", eot_);
|
|
settings_->setValue("MFICharacter", mfi_);
|
|
settings_->setValue("MyInfo", my_info_);
|
|
settings_->setValue("MyStatus", my_status_);
|
|
settings_->setValue("CQMessage", cq_);
|
|
settings_->setValue("HBMessage", hb_);
|
|
settings_->setValue("Reply", reply_);
|
|
settings_->setValue("CallsignAging", callsign_aging_);
|
|
settings_->setValue("ActivityAging", activity_aging_);
|
|
settings_->setValue("colorCQ", color_cq_);
|
|
settings_->setValue("colorPrimary", color_primary_highlight_);
|
|
settings_->setValue("colorSecondary", color_secondary_highlight_);
|
|
settings_->setValue("colorMyCall", color_mycall_);
|
|
settings_->setValue("color_rx_background", color_rx_background_);
|
|
settings_->setValue("color_rx_foreground", color_rx_foreground_);
|
|
settings_->setValue("color_compose_background", color_compose_background_);
|
|
settings_->setValue("color_compose_foreground", color_compose_foreground_);
|
|
settings_->setValue("color_tx_foreground", color_tx_foreground_);
|
|
settings_->setValue("colorDXCC", color_DXCC_);
|
|
settings_->setValue("colorNewCall", color_NewCall_);
|
|
|
|
settings_->setValue("colorTableBackground", color_table_background_);
|
|
settings_->setValue("colorTableHighlight", color_table_highlight_);
|
|
settings_->setValue("colorTableForeground", color_table_foreground_);
|
|
|
|
settings_->setValue("PillsEnabled", pills_enabled_);
|
|
settings_->setValue("colorPillRecipientBg", color_pill_recipient_bg_);
|
|
settings_->setValue("colorPillRecipientFg", color_pill_recipient_fg_);
|
|
settings_->setValue("colorPillCommandBg", color_pill_command_bg_);
|
|
settings_->setValue("colorPillCommandFg", color_pill_command_fg_);
|
|
settings_->setValue("colorPillGroupBg", color_pill_group_bg_);
|
|
settings_->setValue("colorPillGroupFg", color_pill_group_fg_);
|
|
settings_->setValue("colorPillSenderBg", color_pill_sender_bg_);
|
|
settings_->setValue("colorPillSenderFg", color_pill_sender_fg_);
|
|
|
|
settings_->setValue("Font", font_.toString());
|
|
settings_->setValue("RXTextFont", rx_text_font_.toString());
|
|
settings_->setValue("TXTextFont", tx_text_font_.toString());
|
|
settings_->setValue("composeTextFont", compose_text_font_.toString());
|
|
settings_->setValue("tableFont", table_font_.toString());
|
|
|
|
settings_->setValue("TxDelay", txDelay_);
|
|
settings_->setValue("PTTMethod", QVariant::fromValue(rig_params_.ptt_type));
|
|
settings_->setValue("PTTport", rig_params_.ptt_port);
|
|
settings_->setValue("SaveDir", save_directory_.absolutePath());
|
|
if (!audio_input_device_.isNull()) {
|
|
settings_->setValue("SoundInId", audio_input_device_.id());
|
|
settings_->setValue("SoundInName", audio_input_device_.description()); // legacy/migration hint
|
|
settings_->setValue("AudioInputChannel", AudioDevice::toString(audio_input_channel_));
|
|
}
|
|
|
|
if (!audio_output_device_.isNull()) {
|
|
settings_->setValue("SoundOutId", audio_output_device_.id());
|
|
settings_->setValue("SoundOutName", audio_output_device_.description()); // legacy/migration hint
|
|
settings_->setValue("AudioOutputChannel", AudioDevice::toString(audio_output_channel_));
|
|
}
|
|
|
|
if (!notification_audio_output_device_.isNull()) {
|
|
settings_->setValue("NotificationSoundOutId", notification_audio_output_device_.id());
|
|
settings_->setValue("NotificationSoundOutName", notification_audio_output_device_.description()); // legacy/migration hint
|
|
}
|
|
settings_->setValue("TransmitDirected", transmit_directed_);
|
|
settings_->setValue("AutoreplyOnAtStartup", autoreply_on_at_startup_);
|
|
settings_->setValue("AutoreplyConfirmation", autoreply_confirmation_);
|
|
settings_->setValue("BeaconAnywhere", heartbeat_anywhere_);
|
|
settings_->setValue("HeartbeatQSOPause", heartbeat_qso_pause_);
|
|
settings_->setValue("HeartbeatAckSNR", heartbeat_ack_snr_);
|
|
settings_->setValue("RelayOFF", relay_disabled_);
|
|
settings_->setValue("MonitorOFF", monitor_off_at_startup_);
|
|
settings_->setValue("TransmitOFF", transmit_off_at_startup_);
|
|
settings_->setValue("MonitorLastUsed", monitor_last_used_);
|
|
settings_->setValue("PSKReporter", spot_to_reporting_networks_);
|
|
settings_->setValue("SpotToAPRS", spot_to_aprs_);
|
|
settings_->setValue("WriteLogs", write_logs_);
|
|
settings_->setValue("ResetActivity", reset_activity_);
|
|
settings_->setValue("CheckForUpdates", check_for_updates_);
|
|
settings_->setValue("TxQSYAllowed", tx_qsy_allowed_);
|
|
settings_->setValue("Macros", macros_.stringList());
|
|
settings_->setValue(versionedFrequenciesSettingsKey,
|
|
QVariant::fromValue(frequencies_.frequency_list()));
|
|
settings_->setValue("stations",
|
|
QVariant::fromValue(stations_.station_list()));
|
|
settings_->setValue("Rig", rig_params_.rig_name);
|
|
settings_->setValue("CATNetworkPort", rig_params_.network_port);
|
|
settings_->setValue("CATUSBPort", rig_params_.usb_port);
|
|
settings_->setValue("CATSerialPort", rig_params_.serial_port);
|
|
settings_->setValue("CATSerialRate", rig_params_.baud);
|
|
settings_->setValue("CATDataBits",
|
|
QVariant::fromValue(rig_params_.data_bits));
|
|
settings_->setValue("CATStopBits",
|
|
QVariant::fromValue(rig_params_.stop_bits));
|
|
settings_->setValue("CATHandshake",
|
|
QVariant::fromValue(rig_params_.handshake));
|
|
settings_->setValue("DataMode", QVariant::fromValue(data_mode_));
|
|
settings_->setValue("InsertBlank", insert_blank_);
|
|
settings_->setValue("DXCCEntity", DXCC_);
|
|
settings_->setValue("PrincipalPrefix", ppfx_);
|
|
settings_->setValue("Miles", miles_);
|
|
settings_->setValue("HoldPTT", hold_ptt_);
|
|
settings_->setValue("AvoidForcedIdentify", avoid_forced_identify_);
|
|
settings_->setValue("AvoidAllcall", avoid_allcall_);
|
|
settings_->setValue("Spellcheck", spellcheck_);
|
|
settings_->setValue("TxBeacon", heartbeat_);
|
|
settings_->setValue("TxIdleWatchdog", watchdog_);
|
|
settings_->setValue("Tx2QSO", TX_messages_);
|
|
settings_->setValue("CATForceDTR", rig_params_.force_dtr);
|
|
settings_->setValue("DTR", rig_params_.dtr_high);
|
|
settings_->setValue("CATForceRTS", rig_params_.force_rts);
|
|
settings_->setValue("RTS", rig_params_.rts_high);
|
|
settings_->setValue("TXAudioSource",
|
|
QVariant::fromValue(rig_params_.audio_source));
|
|
settings_->setValue("Polling", rig_params_.poll_interval);
|
|
settings_->setValue("SplitMode",
|
|
QVariant::fromValue(rig_params_.split_mode));
|
|
settings_->setValue("OpCall", opCall_);
|
|
settings_->setValue("PTTCommand", ptt_command_);
|
|
settings_->setValue("aprsServer", aprs_server_name_);
|
|
settings_->setValue("aprsServerPort", aprs_server_port_);
|
|
settings_->setValue("APRSSpotting", spot_to_aprs_);
|
|
settings_->setValue("APRSISRelay", spot_to_aprs_relay_);
|
|
settings_->setValue("UDPServer", udp_server_name_);
|
|
settings_->setValue("UDPServerPort", udp_server_port_);
|
|
// WSJT-X Protocol settings
|
|
settings_->setValue("WSJTXProtocolEnabled", wsjtx_protocol_enabled_);
|
|
settings_->setValue("WSJTXServer", wsjtx_server_name_);
|
|
settings_->setValue("WSJTXServerPort", wsjtx_server_port_);
|
|
settings_->setValue("WSJTXTTL", wsjtx_TTL_);
|
|
settings_->setValue("WSJTXAcceptRequests", wsjtx_accept_requests_);
|
|
settings_->setValue("WSJTXInterfaces", wsjtx_interface_names_);
|
|
settings_->setValue("TCPServer", tcp_server_name_);
|
|
settings_->setValue("TCPServerPort", tcp_server_port_);
|
|
settings_->setValue("N3FJPServer", n3fjp_server_name_);
|
|
settings_->setValue("N3FJPServerPort", n3fjp_server_port_);
|
|
settings_->setValue("BroadcastToN3FJP", broadcast_to_n3fjp_);
|
|
settings_->setValue("N1MMServer", n1mm_server_name_);
|
|
settings_->setValue("N1MMServerPort", n1mm_server_port_);
|
|
settings_->setValue("BroadcastToN1MM", broadcast_to_n1mm_);
|
|
settings_->setValue("AcceptUDPRequests", accept_udp_requests_);
|
|
settings_->setValue("AcceptTCPRequests", accept_tcp_requests_);
|
|
settings_->setValue("UDPEnabled", udpEnabled_);
|
|
settings_->setValue("TCPEnabled", tcpEnabled_);
|
|
settings_->setValue("TCPMaxConnections", tcpMaxConnections_);
|
|
settings_->setValue("CalibrationIntercept", calibration_.intercept);
|
|
settings_->setValue("CalibrationSlopePPM", calibration_.slope_ppm);
|
|
settings_->setValue("pwrBandTxMemory", pwrBandTxMemory_);
|
|
settings_->setValue("pwrBandTuneMemory", pwrBandTuneMemory_);
|
|
settings_->setValue("Region", QVariant::fromValue(region_));
|
|
settings_->setValue("AutoGrid", use_dynamic_info_);
|
|
|
|
// notifications
|
|
settings_->setValue("EnableNotifications", enable_notifications_);
|
|
settings_->beginGroup("Notifications");
|
|
{
|
|
foreach (auto key, notifications_enabled_.keys()) {
|
|
settings_->beginGroup(key);
|
|
{
|
|
settings_->setValue(
|
|
"enabled",
|
|
QVariant(notifications_enabled_.value(key, false)));
|
|
settings_->setValue(
|
|
"path", QVariant(notifications_paths_.value(key, "")));
|
|
}
|
|
settings_->endGroup();
|
|
}
|
|
}
|
|
settings_->endGroup();
|
|
}
|
|
|
|
void Configuration::impl::set_rig_invariants() {
|
|
auto const &rig = ui_->rig_combo_box->currentText();
|
|
auto const &ptt_port = ui_->PTT_port_combo_box->currentText();
|
|
auto ptt_method = static_cast<TransceiverFactory::PTTMethod>(
|
|
ui_->PTT_method_button_group->checkedId());
|
|
|
|
auto CAT_PTT_enabled = transceiver_factory_.has_CAT_PTT(rig);
|
|
auto CAT_indirect_serial_PTT =
|
|
transceiver_factory_.has_CAT_indirect_serial_PTT(rig);
|
|
auto asynchronous_CAT = transceiver_factory_.has_asynchronous_CAT(rig);
|
|
auto is_hw_handshake =
|
|
ui_->CAT_handshake_group_box->isEnabled() &&
|
|
TransceiverFactory::handshake_hardware ==
|
|
static_cast<TransceiverFactory::Handshake>(
|
|
ui_->CAT_handshake_button_group->checkedId());
|
|
|
|
ui_->test_CAT_push_button->setStyleSheet({});
|
|
|
|
ui_->CAT_poll_interval_label->setEnabled(!asynchronous_CAT);
|
|
ui_->CAT_poll_interval_spin_box->setEnabled(!asynchronous_CAT);
|
|
|
|
auto port_type = transceiver_factory_.CAT_port_type(rig);
|
|
|
|
bool is_serial_CAT(TransceiverFactory::Capabilities::serial == port_type);
|
|
auto const &cat_port = ui_->CAT_port_combo_box->currentText();
|
|
|
|
// only enable CAT option if transceiver has CAT PTT
|
|
ui_->PTT_CAT_radio_button->setEnabled(CAT_PTT_enabled);
|
|
|
|
auto enable_ptt_port = TransceiverFactory::PTT_method_CAT != ptt_method &&
|
|
TransceiverFactory::PTT_method_VOX != ptt_method;
|
|
ui_->PTT_port_combo_box->setEnabled(enable_ptt_port);
|
|
// if PTT port is not enabled, fill it with the text of the CAT port
|
|
if (!enable_ptt_port) {
|
|
ui_->PTT_port_combo_box->lineEdit()->setText(
|
|
ui_->CAT_port_combo_box->currentText());
|
|
}
|
|
ui_->PTT_port_label->setEnabled(enable_ptt_port);
|
|
|
|
dynamic_cast<QStandardItemModel *>(ui_->PTT_port_combo_box->model())
|
|
->item(ui_->PTT_port_combo_box->findText("CAT"))
|
|
->setEnabled(CAT_indirect_serial_PTT);
|
|
|
|
if (!CAT_indirect_serial_PTT) {
|
|
if ("CAT" == ui_->PTT_port_combo_box->currentText() &&
|
|
ui_->PTT_port_combo_box->currentIndex() > 0) {
|
|
ui_->PTT_port_combo_box->setCurrentIndex(
|
|
ui_->PTT_port_combo_box->currentIndex() - 1);
|
|
}
|
|
}
|
|
ui_->PTT_RTS_radio_button->setEnabled(
|
|
!(is_serial_CAT && ptt_port == cat_port && is_hw_handshake));
|
|
|
|
if (TransceiverFactory::basic_transceiver_name_ == rig) {
|
|
// makes no sense with rig as "None"
|
|
ui_->monitor_last_used_check_box->setEnabled(false);
|
|
|
|
ui_->catTab->setEnabled(false);
|
|
// ui_->CAT_control_group_box->setEnabled (false);
|
|
ui_->test_CAT_push_button->setEnabled(false);
|
|
ui_->test_PTT_push_button->setEnabled(
|
|
TransceiverFactory::PTT_method_DTR == ptt_method ||
|
|
TransceiverFactory::PTT_method_RTS == ptt_method);
|
|
ui_->TX_audio_source_group_box->setEnabled(false);
|
|
} else {
|
|
ui_->monitor_last_used_check_box->setEnabled(true);
|
|
ui_->catTab->setEnabled(true);
|
|
// ui_->CAT_control_group_box->setEnabled (true);
|
|
ui_->test_CAT_push_button->setEnabled(true);
|
|
ui_->test_PTT_push_button->setEnabled(false);
|
|
ui_->TX_audio_source_group_box->setEnabled(
|
|
transceiver_factory_.has_CAT_PTT_mic_data(rig) &&
|
|
TransceiverFactory::PTT_method_CAT == ptt_method);
|
|
if (port_type != last_port_type_) {
|
|
last_port_type_ = port_type;
|
|
switch (port_type) {
|
|
case TransceiverFactory::Capabilities::serial:
|
|
fill_port_combo_box(ui_->CAT_port_combo_box);
|
|
ui_->CAT_port_combo_box->setCurrentText(
|
|
rig_params_.serial_port);
|
|
if (ui_->CAT_port_combo_box->currentText().isEmpty() &&
|
|
ui_->CAT_port_combo_box->count()) {
|
|
ui_->CAT_port_combo_box->setCurrentText(
|
|
ui_->CAT_port_combo_box->itemText(0));
|
|
}
|
|
ui_->CAT_port_label->setText(tr("Serial Port:"));
|
|
ui_->CAT_port_combo_box->setToolTip(
|
|
tr("Serial port used for CAT control"));
|
|
ui_->CAT_port_combo_box->setEnabled(true);
|
|
break;
|
|
|
|
case TransceiverFactory::Capabilities::network:
|
|
ui_->CAT_port_combo_box->clear();
|
|
ui_->CAT_port_combo_box->setCurrentText(
|
|
rig_params_.network_port);
|
|
ui_->CAT_port_label->setText(tr("Network Server:"));
|
|
ui_->CAT_port_combo_box->setToolTip(
|
|
tr("Optional hostname and port of network service.\n"
|
|
"Leave blank for a sensible default on this machine.\n"
|
|
"Formats:\n"
|
|
"\thostname:port\n"
|
|
"\tIPv4-address:port\n"
|
|
"\t[IPv6-address]:port"));
|
|
ui_->CAT_port_combo_box->setEnabled(true);
|
|
break;
|
|
|
|
case TransceiverFactory::Capabilities::usb:
|
|
ui_->CAT_port_combo_box->clear();
|
|
ui_->CAT_port_combo_box->setCurrentText(rig_params_.usb_port);
|
|
ui_->CAT_port_label->setText(tr("USB Device:"));
|
|
ui_->CAT_port_combo_box->setToolTip(
|
|
tr("Optional device identification.\n"
|
|
"Leave blank for a sensible default for the rig.\n"
|
|
"Format:\n"
|
|
"\t[VID[:PID[:VENDOR[:PRODUCT]]]]"));
|
|
ui_->CAT_port_combo_box->setEnabled(true);
|
|
break;
|
|
|
|
default:
|
|
ui_->CAT_port_combo_box->clear();
|
|
ui_->CAT_port_combo_box->setEnabled(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ui_->CAT_serial_port_parameters_group_box->setEnabled(is_serial_CAT);
|
|
|
|
ui_->force_DTR_combo_box->setEnabled(
|
|
is_serial_CAT &&
|
|
(cat_port != ptt_port || !ui_->PTT_DTR_radio_button->isEnabled() ||
|
|
!ui_->PTT_DTR_radio_button->isChecked()));
|
|
ui_->force_RTS_combo_box->setEnabled(
|
|
is_serial_CAT && !is_hw_handshake &&
|
|
(cat_port != ptt_port || !ui_->PTT_RTS_radio_button->isEnabled() ||
|
|
!ui_->PTT_RTS_radio_button->isChecked()));
|
|
}
|
|
ui_->mode_group_box->setEnabled(
|
|
WSJT_RIG_NONE_CAN_SPLIT ||
|
|
TransceiverFactory::basic_transceiver_name_ != rig);
|
|
ui_->split_operation_group_box->setEnabled(
|
|
WSJT_RIG_NONE_CAN_SPLIT ||
|
|
TransceiverFactory::basic_transceiver_name_ != rig);
|
|
}
|
|
|
|
QStringList splitGroups(QString groupsString, bool filter) {
|
|
QStringList groups;
|
|
if (groupsString.isEmpty()) {
|
|
return groups;
|
|
}
|
|
|
|
foreach (QString group, groupsString.split(",")) {
|
|
auto g = group.trimmed();
|
|
if (filter && !g.startsWith("@")) {
|
|
continue;
|
|
}
|
|
groups.append(group.trimmed().toUpper());
|
|
}
|
|
|
|
return groups;
|
|
}
|
|
|
|
QStringList splitWords(QString callsString) {
|
|
QStringList calls;
|
|
if (callsString.isEmpty()) {
|
|
return calls;
|
|
}
|
|
|
|
foreach (QString call, callsString.split(",")) {
|
|
auto g = call.trimmed();
|
|
calls.append(call.trimmed().toUpper());
|
|
}
|
|
|
|
return calls;
|
|
}
|
|
|
|
bool Configuration::impl::validate() {
|
|
if (ui_->eot_line_edit->text().trimmed().left(2).isEmpty()) {
|
|
JS8MessageBox::critical_message(
|
|
this, tr("Please enter an end of transmission character"));
|
|
return false;
|
|
}
|
|
|
|
auto callsign = ui_->callsign_line_edit->text().toUpper().trimmed();
|
|
if (!Varicode::isValidCallsign(callsign, nullptr) ||
|
|
callsign.startsWith("@")) {
|
|
JS8MessageBox::critical_message(
|
|
this, tr("The callsign format you provided is not supported"));
|
|
return false;
|
|
}
|
|
|
|
if (!ui_->grid_line_edit->hasAcceptableInput()) {
|
|
JS8MessageBox::critical_message(
|
|
this, tr("The grid you provided is not valid"));
|
|
return false;
|
|
}
|
|
|
|
foreach (
|
|
auto group,
|
|
splitGroups(ui_->groups_line_edit->text().toUpper().trimmed(), false)) {
|
|
if (!Varicode::isGroupAllowed(group)) {
|
|
JS8MessageBox::critical_message(
|
|
this,
|
|
QString("%1 is a group that cannot be joined").arg(group));
|
|
return false;
|
|
}
|
|
if (!Varicode::isCompoundCallsign(group)) {
|
|
JS8MessageBox::critical_message(
|
|
this, QString("%1 is not a valid group").arg(group));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
foreach (
|
|
auto call,
|
|
splitWords(ui_->auto_whitelist_line_edit->text().toUpper().trimmed())) {
|
|
if (!Varicode::isValidCallsign(call, nullptr)) {
|
|
JS8MessageBox::critical_message(
|
|
this,
|
|
QString("%1 is not a valid callsign to whitelist").arg(call));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto hb = ui_->hb_message_line_edit->text().toUpper().trimmed();
|
|
if (!hb.isEmpty() && !(hb.startsWith("HB") || hb.contains(callsign))) {
|
|
JS8MessageBox::critical_message(
|
|
this, QString("The HB message format is invalid. It must either "
|
|
"start with \"HB\" or contain your callsign."));
|
|
return false;
|
|
}
|
|
|
|
auto cq = ui_->cq_message_line_edit->text().toUpper().trimmed();
|
|
if (!cq.isEmpty() && !(cq.startsWith("CQ") || cq.contains(callsign))) {
|
|
JS8MessageBox::critical_message(
|
|
this, QString("The CQ message format is invalid. It must either "
|
|
"start with \"CQ\" or contain your callsign."));
|
|
return false;
|
|
}
|
|
|
|
if ((ui_->sound_input_combo_box->currentIndex() < 0) &&
|
|
next_audio_input_device_.isNull()) {
|
|
find_tab(ui_->sound_input_combo_box);
|
|
JS8MessageBox::critical_message(this, tr("Invalid audio input device"));
|
|
return false;
|
|
}
|
|
|
|
if ((ui_->sound_input_channel_combo_box->currentIndex() < 0) &&
|
|
next_audio_input_device_.isNull()) {
|
|
find_tab(ui_->sound_input_combo_box);
|
|
JS8MessageBox::critical_message(this, tr("Invalid audio input device"));
|
|
return false;
|
|
}
|
|
|
|
if ((ui_->sound_output_combo_box->currentIndex() < 0) &&
|
|
next_audio_output_device_.isNull()) {
|
|
find_tab(ui_->sound_output_combo_box);
|
|
JS8MessageBox::information_message(this,
|
|
tr("Invalid audio output device"));
|
|
// don't reject as we can work without an audio output
|
|
}
|
|
|
|
if ((ui_->notification_sound_output_combo_box->currentIndex() < 0) &&
|
|
next_notification_audio_output_device_.isNull()) {
|
|
find_tab(ui_->notification_sound_output_combo_box);
|
|
JS8MessageBox::information_message(
|
|
this, tr("Invalid notification audio output device"));
|
|
// don't reject as we can work without a notification audio output
|
|
}
|
|
|
|
if (!ui_->PTT_method_button_group->checkedButton()->isEnabled()) {
|
|
JS8MessageBox::critical_message(this, tr("Invalid PTT method"));
|
|
return false;
|
|
}
|
|
|
|
auto ptt_method = static_cast<TransceiverFactory::PTTMethod>(
|
|
ui_->PTT_method_button_group->checkedId());
|
|
if (ptt_method == TransceiverFactory::PTT_method_DTR ||
|
|
ptt_method == TransceiverFactory::PTT_method_RTS) {
|
|
const auto ptt_port = ui_->PTT_port_combo_box->currentText();
|
|
auto *model = dynamic_cast<QStandardItemModel *>(ui_->PTT_port_combo_box->model());
|
|
const int index = ui_->PTT_port_combo_box->findText(ptt_port);
|
|
QStandardItem *item = (index >= 0 && model) ? model->item(index) : nullptr;
|
|
|
|
const bool enabled = item && item->isEnabled();
|
|
const bool invalid = ptt_port.isEmpty() || !enabled;
|
|
|
|
if (invalid) {
|
|
JS8MessageBox::critical_message(this, tr("Invalid PTT port"));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int Configuration::impl::exec() {
|
|
// macros can be modified in the main window
|
|
next_macros_.setStringList(macros_.stringList());
|
|
|
|
have_rig_ = rig_active_; // record that we started with a rig open
|
|
saved_rig_params_ = rig_params_; // used to detect changes that
|
|
// require the Transceiver to be
|
|
// re-opened
|
|
rig_changed_ = false;
|
|
|
|
initialize_models();
|
|
return QDialog::exec();
|
|
}
|
|
|
|
TransceiverFactory::ParameterPack Configuration::impl::gather_rig_data() const {
|
|
TransceiverFactory::ParameterPack result;
|
|
result.rig_name = ui_->rig_combo_box->currentText();
|
|
|
|
switch (transceiver_factory_.CAT_port_type(result.rig_name)) {
|
|
case TransceiverFactory::Capabilities::network:
|
|
result.network_port = ui_->CAT_port_combo_box->currentText();
|
|
result.usb_port = rig_params_.usb_port;
|
|
result.serial_port = rig_params_.serial_port;
|
|
break;
|
|
|
|
case TransceiverFactory::Capabilities::usb:
|
|
result.usb_port = ui_->CAT_port_combo_box->currentText();
|
|
result.network_port = rig_params_.network_port;
|
|
result.serial_port = rig_params_.serial_port;
|
|
break;
|
|
|
|
default:
|
|
result.serial_port = ui_->CAT_port_combo_box->currentText();
|
|
result.network_port = rig_params_.network_port;
|
|
result.usb_port = rig_params_.usb_port;
|
|
break;
|
|
}
|
|
|
|
result.baud = ui_->CAT_serial_baud_combo_box->currentText().toInt();
|
|
result.data_bits = static_cast<TransceiverFactory::DataBits>(
|
|
ui_->CAT_data_bits_button_group->checkedId());
|
|
result.stop_bits = static_cast<TransceiverFactory::StopBits>(
|
|
ui_->CAT_stop_bits_button_group->checkedId());
|
|
result.handshake = static_cast<TransceiverFactory::Handshake>(
|
|
ui_->CAT_handshake_button_group->checkedId());
|
|
result.force_dtr = ui_->force_DTR_combo_box->isEnabled() &&
|
|
ui_->force_DTR_combo_box->currentIndex() > 0;
|
|
result.dtr_high = ui_->force_DTR_combo_box->isEnabled() &&
|
|
1 == ui_->force_DTR_combo_box->currentIndex();
|
|
result.force_rts = ui_->force_RTS_combo_box->isEnabled() &&
|
|
ui_->force_RTS_combo_box->currentIndex() > 0;
|
|
result.rts_high = ui_->force_RTS_combo_box->isEnabled() &&
|
|
1 == ui_->force_RTS_combo_box->currentIndex();
|
|
result.poll_interval = ui_->CAT_poll_interval_spin_box->value();
|
|
result.ptt_type = static_cast<TransceiverFactory::PTTMethod>(
|
|
ui_->PTT_method_button_group->checkedId());
|
|
|
|
// don't allow CAT for None rig
|
|
if (result.rig_name == "None" &&
|
|
result.ptt_type == TransceiverFactory::PTT_method_CAT) {
|
|
result.ptt_type = TransceiverFactory::PTT_method_VOX;
|
|
}
|
|
|
|
result.ptt_port = ui_->PTT_port_combo_box->currentText();
|
|
result.audio_source = static_cast<TransceiverFactory::TXAudioSource>(
|
|
ui_->TX_audio_source_button_group->checkedId());
|
|
result.split_mode = static_cast<TransceiverFactory::SplitMode>(
|
|
ui_->split_mode_button_group->checkedId());
|
|
return result;
|
|
}
|
|
|
|
void Configuration::impl::accept() {
|
|
// Called when OK button is clicked.
|
|
|
|
if (!validate()) {
|
|
return; // not accepting
|
|
}
|
|
|
|
// extract all rig related configuration parameters into temporary
|
|
// structure for checking if the rig needs re-opening without
|
|
// actually updating our live state
|
|
auto temp_rig_params = gather_rig_data();
|
|
|
|
// open_rig() uses values from models so we use it to validate the
|
|
// Transceiver settings before agreeing to accept the configuration
|
|
if (temp_rig_params != rig_params_ && !open_rig()) {
|
|
return; // not accepting
|
|
}
|
|
|
|
QDialog::accept(); // do this before accessing custom
|
|
// models so that any changes in
|
|
// delegates in views get flushed to
|
|
// the underlying models before we
|
|
// access them
|
|
|
|
sync_transceiver(true); // force an update
|
|
|
|
//
|
|
// from here on we are bound to accept the new configuration
|
|
// parameters so extract values from models and make them live
|
|
//
|
|
|
|
if (next_font_ != font_) {
|
|
font_ = next_font_;
|
|
Q_EMIT self_->gui_text_font_changed(font_);
|
|
}
|
|
|
|
if (next_tx_text_font_ != tx_text_font_) {
|
|
tx_text_font_ = next_tx_text_font_;
|
|
Q_EMIT self_->tx_text_font_changed(tx_text_font_);
|
|
}
|
|
|
|
if (next_rx_text_font_ != rx_text_font_) {
|
|
rx_text_font_ = next_rx_text_font_;
|
|
Q_EMIT self_->rx_text_font_changed(rx_text_font_);
|
|
}
|
|
|
|
if (next_compose_text_font_ != compose_text_font_) {
|
|
compose_text_font_ = next_compose_text_font_;
|
|
Q_EMIT self_->compose_text_font_changed(compose_text_font_);
|
|
}
|
|
|
|
if (next_table_font_ != table_font_) {
|
|
table_font_ = next_table_font_;
|
|
Q_EMIT self_->table_font_changed(table_font_);
|
|
}
|
|
|
|
color_primary_highlight_ = next_color_primary_highlight_;
|
|
color_secondary_highlight_ = next_color_secondary_highlight_;
|
|
color_cq_ = next_color_cq_;
|
|
color_mycall_ = next_color_mycall_;
|
|
color_rx_background_ = next_color_rx_background_;
|
|
color_rx_foreground_ = next_color_rx_foreground_;
|
|
color_compose_background_ = next_color_compose_background_;
|
|
color_compose_foreground_ = next_color_compose_foreground_;
|
|
color_tx_foreground_ = next_color_tx_foreground_;
|
|
color_DXCC_ = next_color_DXCC_;
|
|
color_NewCall_ = next_color_NewCall_;
|
|
color_table_background_ = next_color_table_background_;
|
|
color_table_highlight_ = next_color_table_highlight_;
|
|
color_table_foreground_ = next_color_table_foreground_;
|
|
pills_enabled_ = ui_->pills_enabled_check_box->isChecked();
|
|
color_pill_recipient_bg_ = next_color_pill_recipient_bg_;
|
|
color_pill_recipient_fg_ = next_color_pill_recipient_fg_;
|
|
color_pill_command_bg_ = next_color_pill_command_bg_;
|
|
color_pill_command_fg_ = next_color_pill_command_fg_;
|
|
color_pill_group_bg_ = next_color_pill_group_bg_;
|
|
color_pill_group_fg_ = next_color_pill_group_fg_;
|
|
color_pill_sender_bg_ = next_color_pill_sender_bg_;
|
|
color_pill_sender_fg_ = next_color_pill_sender_fg_;
|
|
|
|
Q_EMIT self_->colors_changed();
|
|
|
|
rig_params_ = temp_rig_params; // now we can go live with the rig
|
|
// related configuration parameters
|
|
rig_is_dummy_ =
|
|
TransceiverFactory::basic_transceiver_name_ == rig_params_.rig_name;
|
|
|
|
{
|
|
auto const selected_device =
|
|
ui_->sound_input_combo_box->currentData().value<QAudioDevice>();
|
|
if (dev_id(selected_device) != dev_id(next_audio_input_device_)) {
|
|
next_audio_input_device_ = selected_device;
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const selected_device =
|
|
ui_->sound_output_combo_box->currentData().value<QAudioDevice>();
|
|
if (dev_id(selected_device) != dev_id(next_audio_output_device_)) {
|
|
next_audio_output_device_ = selected_device;
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const selected_device =
|
|
ui_->notification_sound_output_combo_box->currentData().value<QAudioDevice>();
|
|
if (dev_id(selected_device) != dev_id(next_notification_audio_output_device_)) {
|
|
next_notification_audio_output_device_ = selected_device;
|
|
}
|
|
}
|
|
|
|
if (auto const selected_channel = static_cast<AudioDevice::Channel>(
|
|
ui_->sound_input_channel_combo_box->currentIndex());
|
|
selected_channel != next_audio_input_channel_) {
|
|
next_audio_input_channel_ = selected_channel;
|
|
}
|
|
Q_ASSERT(next_audio_input_channel_ <= AudioDevice::Right);
|
|
|
|
if (auto const selected_channel = static_cast<AudioDevice::Channel>(
|
|
ui_->sound_output_channel_combo_box->currentIndex());
|
|
selected_channel != next_audio_output_channel_) {
|
|
next_audio_output_channel_ = selected_channel;
|
|
}
|
|
Q_ASSERT(next_audio_output_channel_ <= AudioDevice::Both);
|
|
|
|
if (audio_input_device_ != next_audio_input_device_ ||
|
|
next_audio_input_device_.isNull()) {
|
|
audio_input_device_ = next_audio_input_device_;
|
|
restart_sound_input_device_ = true;
|
|
}
|
|
if (audio_input_channel_ != next_audio_input_channel_) {
|
|
audio_input_channel_ = next_audio_input_channel_;
|
|
restart_sound_input_device_ = true;
|
|
}
|
|
|
|
if (audio_output_device_ != next_audio_output_device_ ||
|
|
next_audio_output_device_.isNull()) {
|
|
audio_output_device_ = next_audio_output_device_;
|
|
restart_sound_output_device_ = true;
|
|
}
|
|
if (audio_output_channel_ != next_audio_output_channel_) {
|
|
audio_output_channel_ = next_audio_output_channel_;
|
|
restart_sound_output_device_ = true;
|
|
}
|
|
|
|
if (notification_audio_output_device_ !=
|
|
next_notification_audio_output_device_ ||
|
|
next_notification_audio_output_device_.isNull()) {
|
|
notification_audio_output_device_ =
|
|
next_notification_audio_output_device_;
|
|
restart_notification_sound_output_device_ = true;
|
|
}
|
|
|
|
qCDebug(configuration_js8)
|
|
<< "Configure::accept: audio i/p:" << audio_input_device_.description()
|
|
<< "chan:" << audio_input_channel_
|
|
<< "o/p:" << audio_output_device_.description()
|
|
<< "chan:" << audio_output_channel_
|
|
<< "n:" << notification_audio_output_device_.description()
|
|
<< "reset i/p:" << restart_sound_input_device_
|
|
<< "reset o/p:" << restart_sound_output_device_
|
|
<< "reset n:" << restart_notification_sound_output_device_;
|
|
|
|
my_callsign_ = ui_->callsign_line_edit->text().toUpper().trimmed();
|
|
my_grid_ = ui_->grid_line_edit->text().toUpper().trimmed();
|
|
my_groups_ =
|
|
splitGroups(ui_->groups_line_edit->text().toUpper().trimmed(), true);
|
|
auto_whitelist_ =
|
|
splitWords(ui_->auto_whitelist_line_edit->text().toUpper().trimmed());
|
|
auto_blacklist_ =
|
|
splitWords(ui_->auto_blacklist_line_edit->text().toUpper().trimmed());
|
|
hb_blacklist_ =
|
|
splitWords(ui_->hb_blacklist_line_edit->text().toUpper().trimmed());
|
|
spot_blacklist_ =
|
|
splitWords(ui_->spot_blacklist_line_edit->text().toUpper().trimmed());
|
|
rx_callsign_blocklist_ =
|
|
splitWords(ui_->rx_blocklist_line_edit->text().toUpper().trimmed());
|
|
primary_highlight_words_ =
|
|
splitWords(ui_->primaryHighlightLineEdit->text().toUpper().trimmed());
|
|
secondary_highlight_words_ =
|
|
splitWords(ui_->secondaryHighlightLineEdit->text().toUpper().trimmed());
|
|
cq_ = ui_->cq_message_line_edit->text().toUpper();
|
|
hb_ = ui_->hb_message_line_edit->text().toUpper();
|
|
reply_ = ui_->reply_message_line_edit->text().toUpper();
|
|
eot_ = ui_->eot_line_edit->text();
|
|
mfi_ = ui_->mfi_line_edit->text();
|
|
my_info_ = ui_->info_message_line_edit->text().toUpper();
|
|
my_status_ = ui_->status_message_line_edit->text().toUpper();
|
|
callsign_aging_ = ui_->callsign_aging_spin_box->value();
|
|
activity_aging_ = ui_->activity_aging_spin_box->value();
|
|
spot_to_reporting_networks_ = ui_->psk_reporter_check_box->isChecked();
|
|
spot_to_aprs_ = ui_->enable_aprs_spotting_check_box->isChecked();
|
|
txDelay_ = ui_->sbTxDelay->value();
|
|
write_logs_ = ui_->write_logs_check_box->isChecked();
|
|
reset_activity_ = ui_->reset_activity_check_box->isChecked();
|
|
check_for_updates_ = ui_->checkForUpdates_checkBox->isChecked();
|
|
tx_qsy_allowed_ = ui_->tx_qsy_check_box->isChecked();
|
|
transmit_directed_ = ui_->transmit_directed_check_box->isChecked();
|
|
autoreply_on_at_startup_ = ui_->autoreply_on_check_box->isChecked();
|
|
autoreply_confirmation_ =
|
|
ui_->autoreply_confirmation_check_box->isChecked();
|
|
heartbeat_anywhere_ = ui_->heartbeat_anywhere_check_box->isChecked();
|
|
heartbeat_qso_pause_ = ui_->heartbeat_qso_pause_check_box->isChecked();
|
|
heartbeat_ack_snr_ = ui_->heartbeat_ack_snr_check_box->isChecked();
|
|
hb_rate_limit_ = ui_->hb_rate_limit_check_box->isChecked();
|
|
relay_disabled_ = ui_->relay_disabled_check_box->isChecked();
|
|
monitor_off_at_startup_ = ui_->monitor_off_check_box->isChecked();
|
|
transmit_off_at_startup_ = ui_->transmit_off_check_box->isChecked();
|
|
monitor_last_used_ = ui_->monitor_last_used_check_box->isChecked();
|
|
miles_ = ui_->miles_check_box->isChecked();
|
|
hold_ptt_ = ui_->hold_ptt_check_box->isChecked();
|
|
avoid_forced_identify_ = ui_->avoid_forced_identify_check_box->isChecked();
|
|
avoid_allcall_ = ui_->avoid_allcall_check_box->isChecked();
|
|
spellcheck_ = ui_->spellcheck_check_box->isChecked();
|
|
heartbeat_ = ui_->heartbeat_spin_box->value();
|
|
watchdog_ = ui_->tx_watchdog_spin_box->value();
|
|
data_mode_ = static_cast<DataMode>(ui_->TX_mode_button_group->checkedId());
|
|
save_directory_.setPath(ui_->save_path_display_label->text());
|
|
calibration_.intercept = ui_->calibration_intercept_spin_box->value();
|
|
calibration_.slope_ppm = ui_->calibration_slope_ppm_spin_box->value();
|
|
pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked();
|
|
pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked();
|
|
opCall_ = ui_->opCallEntry->text();
|
|
ptt_command_ = ui_->ptt_command_line_edit->text();
|
|
aprs_server_name_ = ui_->aprs_server_line_edit->text();
|
|
aprs_server_port_ = ui_->aprs_server_port_spin_box->value();
|
|
|
|
auto const newAutoSwitchBands =
|
|
ui_->auto_switch_bands_check_box->isChecked();
|
|
auto_switch_bands_ = newAutoSwitchBands;
|
|
|
|
// Emit this even if the value has not changed as a way to reset the
|
|
// scheduler.
|
|
// TODO this is smelly, revisit when we make the scheduler smarter
|
|
Q_EMIT self_->auto_switch_bands_changed(auto_switch_bands_);
|
|
|
|
auto const newUdpEnabled = ui_->udpEnable->isChecked();
|
|
auto const newUdpServerName = ui_->udp_server_line_edit->text();
|
|
if (newUdpServerName != udp_server_name_ || newUdpEnabled != udpEnabled_) {
|
|
udp_server_name_ = newUdpServerName;
|
|
udpEnabled_ = newUdpEnabled;
|
|
|
|
Q_EMIT self_->udp_server_name_changed(udpEnabled_ ? newUdpServerName
|
|
: "");
|
|
}
|
|
|
|
auto newUdpPort = ui_->udp_server_port_spin_box->value();
|
|
if (newUdpPort != udp_server_port_) {
|
|
udp_server_port_ = newUdpPort;
|
|
Q_EMIT self_->udp_server_port_changed(newUdpPort);
|
|
}
|
|
|
|
auto newTcpEnabled = ui_->tcpEnable->isChecked();
|
|
auto newTcpServer = ui_->tcp_server_line_edit->text();
|
|
if (newTcpServer != tcp_server_name_ || newTcpEnabled != tcpEnabled_) {
|
|
tcp_server_name_ = newTcpServer;
|
|
tcpEnabled_ = newTcpEnabled;
|
|
|
|
Q_EMIT self_->tcp_server_changed(tcpEnabled_ ? newTcpServer : "");
|
|
}
|
|
|
|
auto newTcpPort = ui_->tcp_server_port_spin_box->value();
|
|
if (newTcpPort != tcp_server_port_) {
|
|
tcp_server_port_ = newTcpPort;
|
|
Q_EMIT self_->tcp_server_port_changed(newTcpPort);
|
|
}
|
|
|
|
auto newTcpMaxConnections = ui_->tcp_max_connections_spin_box->value();
|
|
if (newTcpMaxConnections != tcpMaxConnections_) {
|
|
tcpMaxConnections_ = newTcpMaxConnections;
|
|
Q_EMIT self_->tcp_max_connections_changed(newTcpMaxConnections);
|
|
}
|
|
|
|
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked();
|
|
accept_tcp_requests_ = ui_->accept_tcp_requests_check_box->isChecked();
|
|
|
|
auto new_n3fjp_server = ui_->n3fjp_server_name_line_edit->text();
|
|
n3fjp_server_name_ = new_n3fjp_server;
|
|
auto new_n3fjp_port = ui_->n3fjp_server_port_spin_box->value();
|
|
n3fjp_server_port_ = new_n3fjp_port;
|
|
broadcast_to_n3fjp_ = ui_->enable_n3fjp_broadcast_check_box->isChecked();
|
|
|
|
auto new_n1mm_server = ui_->n1mm_server_name_line_edit->text();
|
|
n1mm_server_name_ = new_n1mm_server;
|
|
auto new_n1mm_port = ui_->n1mm_server_port_spin_box->value();
|
|
n1mm_server_port_ = new_n1mm_port;
|
|
broadcast_to_n1mm_ = ui_->enable_n1mm_broadcast_check_box->isChecked();
|
|
|
|
// WSJT-X Protocol settings
|
|
auto new_wsjtx_enabled = ui_->wsjtx_enable_check_box->isChecked();
|
|
auto new_wsjtx_server = ui_->wsjtx_server_line_edit->text();
|
|
auto new_wsjtx_port = ui_->wsjtx_server_port_spin_box->value();
|
|
auto new_wsjtx_ttl = ui_->wsjtx_TTL_spin_box->value();
|
|
auto new_wsjtx_accept_requests =
|
|
ui_->wsjtx_accept_requests_check_box->isChecked();
|
|
auto new_wsjtx_interfaces =
|
|
get_selected_network_interfaces(ui_->wsjtx_interfaces_combo_box);
|
|
|
|
if (new_wsjtx_enabled != wsjtx_protocol_enabled_) {
|
|
wsjtx_protocol_enabled_ = new_wsjtx_enabled;
|
|
Q_EMIT self_->wsjtx_protocol_enabled_changed(wsjtx_protocol_enabled_);
|
|
}
|
|
|
|
if (new_wsjtx_server != wsjtx_server_name_ ||
|
|
new_wsjtx_enabled != wsjtx_protocol_enabled_) {
|
|
wsjtx_server_name_ = new_wsjtx_server;
|
|
Q_EMIT self_->wsjtx_server_changed(
|
|
wsjtx_protocol_enabled_ ? new_wsjtx_server : "");
|
|
}
|
|
|
|
if (new_wsjtx_port != wsjtx_server_port_) {
|
|
wsjtx_server_port_ = new_wsjtx_port;
|
|
Q_EMIT self_->wsjtx_server_port_changed(new_wsjtx_port);
|
|
}
|
|
|
|
if (new_wsjtx_ttl != wsjtx_TTL_) {
|
|
wsjtx_TTL_ = new_wsjtx_ttl;
|
|
Q_EMIT self_->wsjtx_TTL_changed(new_wsjtx_ttl);
|
|
}
|
|
|
|
wsjtx_accept_requests_ = new_wsjtx_accept_requests;
|
|
|
|
if (new_wsjtx_interfaces != wsjtx_interface_names_) {
|
|
wsjtx_interface_names_ = new_wsjtx_interfaces;
|
|
Q_EMIT self_->wsjtx_interfaces_changed(wsjtx_interface_names_);
|
|
}
|
|
|
|
if (macros_.stringList() != next_macros_.stringList()) {
|
|
macros_.setStringList(next_macros_.stringList());
|
|
}
|
|
|
|
region_ = IARURegions::value(ui_->region_combo_box->currentText());
|
|
|
|
if (frequencies_.frequency_list() != next_frequencies_.frequency_list()) {
|
|
frequencies_.frequency_list(next_frequencies_.frequency_list());
|
|
frequencies_.sort(FrequencyList_v3::frequency_column);
|
|
}
|
|
|
|
if (stations_.station_list() != next_stations_.station_list()) {
|
|
stations_.station_list(next_stations_.station_list());
|
|
stations_.sort(StationList::switch_at_column);
|
|
|
|
Q_EMIT self_->band_schedule_changed(this->stations_);
|
|
}
|
|
|
|
if (ui_->use_dynamic_grid->isChecked() && !use_dynamic_info_) {
|
|
// turning on so clear it so only the next location update gets used
|
|
dynamic_grid_.clear();
|
|
}
|
|
use_dynamic_info_ = ui_->use_dynamic_grid->isChecked();
|
|
|
|
// notifications
|
|
enable_notifications_ = ui_->notifications_check_box->isChecked();
|
|
|
|
for (int i = 0; i < ui_->notifications_table_widget->rowCount(); i++) {
|
|
// event
|
|
auto eventItem = ui_->notifications_table_widget->item(i, 0);
|
|
auto key = eventItem->data(Qt::UserRole).toString();
|
|
if (key.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
auto enabledWidget = ui_->notifications_table_widget->cellWidget(i, 1);
|
|
QCheckBox *enabledCheckbox =
|
|
enabledWidget->findChild<QCheckBox *>("enabledCheckbox");
|
|
if (enabledCheckbox) {
|
|
notifications_enabled_[key] = enabledCheckbox->isChecked();
|
|
}
|
|
|
|
auto pathWidget = ui_->notifications_table_widget->cellWidget(i, 2);
|
|
QLabel *pathLabel = qobject_cast<QLabel *>(pathWidget);
|
|
if (pathLabel) {
|
|
notifications_paths_[key] = pathLabel->text();
|
|
}
|
|
}
|
|
|
|
write_settings(); // make visible to all
|
|
}
|
|
|
|
void Configuration::impl::reject() {
|
|
initialize_models(); // reverts to settings as at exec ()
|
|
|
|
// check if the Transceiver instance changed, in which case we need
|
|
// to re open any prior Transceiver type
|
|
if (rig_changed_) {
|
|
if (have_rig_) {
|
|
// we have to do this since the rig has been opened since we
|
|
// were exec'ed even though it might fail
|
|
open_rig();
|
|
} else {
|
|
close_rig();
|
|
}
|
|
}
|
|
|
|
QDialog::reject();
|
|
}
|
|
|
|
void Configuration::impl::on_psk_reporter_check_box_toggled(bool checked) {
|
|
ui_->enable_aprs_spotting_check_box->setEnabled(checked);
|
|
|
|
bool spot = ui_->enable_aprs_spotting_check_box->isChecked();
|
|
ui_->aprs_server_line_edit->setEnabled(spot && checked);
|
|
ui_->aprs_server_port_spin_box->setEnabled(spot && checked);
|
|
}
|
|
|
|
void Configuration::impl::on_enable_aprs_spotting_check_box_toggled(
|
|
bool checked) {
|
|
ui_->aprs_server_line_edit->setEnabled(checked);
|
|
ui_->aprs_server_port_spin_box->setEnabled(checked);
|
|
}
|
|
|
|
void Configuration::impl::on_enable_aprs_relay_check_box_toggled(bool checked) {
|
|
spot_to_aprs_relay_ = checked;
|
|
Q_EMIT self_->spot_to_aprs_relay_changed(spot_to_aprs_relay_);
|
|
}
|
|
|
|
void Configuration::impl::on_notifications_check_box_toggled(bool checked) {
|
|
ui_->notifications_table_widget->setEnabled(checked);
|
|
}
|
|
|
|
void Configuration::impl::on_font_push_button_clicked() {
|
|
next_font_ = QFontDialog::getFont(0, next_font_, this, tr("Font Chooser")
|
|
#if QT_VERSION >= 0x050201
|
|
,
|
|
QFontDialog::DontUseNativeDialog
|
|
#endif
|
|
);
|
|
ui_->font_push_button->setText(QString("Application Font (%1 %2)")
|
|
.arg(next_font_.family())
|
|
.arg(next_font_.pointSize()));
|
|
}
|
|
|
|
void Configuration::impl::on_style_push_button_clicked() {
|
|
QString fileName = QFileDialog::getOpenFileName(nullptr,
|
|
"Open Stylesheet", "", "Style Sheets (*.qss);;All Files (*)");
|
|
|
|
if (fileName.isEmpty()) return;
|
|
|
|
QFile file(fileName);
|
|
if (file.open(QFile::ReadOnly | QFile::Text)) {
|
|
QTextStream stream(&file);
|
|
// Apply the stylesheet globally
|
|
qApp->setStyleSheet(stream.readAll());
|
|
file.close();
|
|
}
|
|
|
|
ui_->style_push_button->setText(QString("Application Stylesheet (%1)")
|
|
.arg(QFileInfo(fileName).fileName()));
|
|
}
|
|
|
|
void Configuration::impl::on_tableFontButton_clicked() {
|
|
next_table_font_ =
|
|
QFontDialog::getFont(0, next_table_font_, this, tr("Font Chooser")
|
|
#if QT_VERSION >= 0x050201
|
|
,
|
|
QFontDialog::DontUseNativeDialog
|
|
#endif
|
|
);
|
|
|
|
ui_->tableFontButton->setText(QString("Font (%1 %2)")
|
|
.arg(next_table_font_.family())
|
|
.arg(next_table_font_.pointSize()));
|
|
}
|
|
|
|
QColor getColor(QColor initial, QWidget *parent, QString title) {
|
|
QList<QColor> custom = {QColor("#66FF66"), QColor("#FF6666"),
|
|
QColor("#FFEAA7"), QColor("#3498DB")};
|
|
|
|
auto d = new QColorDialog(initial, parent);
|
|
d->setWindowTitle(title);
|
|
for (int i = 0; i < custom.length(); i++) {
|
|
d->setCustomColor(i, custom.at(i));
|
|
}
|
|
|
|
if (d->exec() == QColorDialog::Accepted) {
|
|
return d->selectedColor();
|
|
} else {
|
|
return initial;
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::updatePillPreview(QLabel *label, const QColor &bg,
|
|
const QColor &fg) {
|
|
label->setStyleSheet(QString("background: %1; color: %2")
|
|
.arg(bg.name())
|
|
.arg(fg.name()));
|
|
}
|
|
|
|
void Configuration::impl::choosePillPreviewColor(
|
|
QColor &target, QLabel *label, const QColor &bg, const QColor &fg,
|
|
const QString &title) {
|
|
auto color = getColor(target, this, title);
|
|
if (!color.isValid())
|
|
return;
|
|
|
|
target = color;
|
|
updatePillPreview(label, bg, fg);
|
|
}
|
|
|
|
void Configuration::impl::on_cqMessagesButton_clicked() {
|
|
auto new_color = getColor(next_color_cq_, this, "CQ Messages Color");
|
|
if (new_color.isValid()) {
|
|
next_color_cq_ = new_color;
|
|
ui_->cqMessagesLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_cq_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_primaryHighlightButton_clicked() {
|
|
auto new_color = getColor(next_color_primary_highlight_, this,
|
|
"Primary Highlight Color");
|
|
if (new_color.isValid()) {
|
|
next_color_primary_highlight_ = new_color;
|
|
ui_->primaryHighlightLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_primary_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_secondaryHighlightButton_clicked() {
|
|
auto new_color = getColor(next_color_secondary_highlight_, this,
|
|
"Secondary Highlight Color");
|
|
if (new_color.isValid()) {
|
|
next_color_secondary_highlight_ = new_color;
|
|
ui_->secondaryHighlightLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_secondary_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_pbMyCall_clicked() {
|
|
auto new_color =
|
|
getColor(next_color_mycall_, this, "Directed Messages Color");
|
|
if (new_color.isValid()) {
|
|
next_color_mycall_ = new_color;
|
|
ui_->labMyCall->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_mycall_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_tableBackgroundButton_clicked() {
|
|
auto new_color =
|
|
getColor(next_color_table_background_, this, "Table Background Color");
|
|
if (new_color.isValid()) {
|
|
next_color_table_background_ = new_color;
|
|
ui_->tableForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_background_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->tableBackgroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_background_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_tableSelectedRowBackgroundButton_clicked() {
|
|
auto new_color = getColor(next_color_table_highlight_, this,
|
|
"Table Selected Row Background Color");
|
|
if (new_color.isValid()) {
|
|
next_color_table_highlight_ = new_color;
|
|
ui_->tableSelectionBackgroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_tableForegroundButton_clicked() {
|
|
auto new_color =
|
|
getColor(next_color_table_foreground_, this, "Table Foreground Color");
|
|
if (new_color.isValid()) {
|
|
next_color_table_foreground_ = new_color;
|
|
ui_->tableForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_background_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->tableBackgroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_background_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->tableSelectionBackgroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_table_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->primaryHighlightLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_primary_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->secondaryHighlightLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_secondary_highlight_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->cqMessagesLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_cq_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
ui_->labMyCall->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_mycall_.name())
|
|
.arg(next_color_table_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_rxBackgroundButton_clicked() {
|
|
auto new_color = getColor(next_color_rx_background_, this,
|
|
"Received Messages Background Color");
|
|
if (new_color.isValid()) {
|
|
next_color_rx_background_ = new_color;
|
|
ui_->rxLabel->setStyleSheet(QString("background: %1; color: %2")
|
|
.arg(color_rx_background_.name())
|
|
.arg(color_rx_foreground_.name()));
|
|
ui_->rxForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_rx_background_.name())
|
|
.arg(next_color_rx_foreground_.name()));
|
|
ui_->txForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_rx_background_.name())
|
|
.arg(next_color_tx_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_rxForegroundButton_clicked() {
|
|
auto new_color = getColor(next_color_rx_foreground_, this,
|
|
"Received Messages Foreground Color");
|
|
if (new_color.isValid()) {
|
|
next_color_rx_foreground_ = new_color;
|
|
ui_->rxLabel->setStyleSheet(QString("background: %1; color: %2")
|
|
.arg(color_rx_background_.name())
|
|
.arg(color_rx_foreground_.name()));
|
|
ui_->rxForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_rx_background_.name())
|
|
.arg(next_color_rx_foreground_.name()));
|
|
ui_->txForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_rx_background_.name())
|
|
.arg(next_color_tx_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_rxFontButton_clicked() {
|
|
next_rx_text_font_ =
|
|
QFontDialog::getFont(0, next_rx_text_font_, this, tr("Font Chooser")
|
|
#if QT_VERSION >= 0x050201
|
|
,
|
|
QFontDialog::DontUseNativeDialog
|
|
#endif
|
|
);
|
|
ui_->rxFontButton->setText(QString("Font (%1 %2)")
|
|
.arg(next_rx_text_font_.family())
|
|
.arg(next_rx_text_font_.pointSize()));
|
|
}
|
|
|
|
void Configuration::impl::on_composeBackgroundButton_clicked() {
|
|
auto new_color = getColor(next_color_compose_background_, this,
|
|
"Compose Messages Background Color");
|
|
if (new_color.isValid()) {
|
|
next_color_compose_background_ = new_color;
|
|
ui_->composeLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_compose_background_.name())
|
|
.arg(next_color_compose_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_composeForegroundButton_clicked() {
|
|
auto new_color = getColor(next_color_compose_foreground_, this,
|
|
"Compose Messages Foreground Color");
|
|
if (new_color.isValid()) {
|
|
next_color_compose_foreground_ = new_color;
|
|
ui_->composeLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_compose_background_.name())
|
|
.arg(next_color_compose_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_txForegroundButton_clicked() {
|
|
auto new_color = getColor(next_color_tx_foreground_, this,
|
|
"Transmitted Messages Foreground Color");
|
|
if (new_color.isValid()) {
|
|
next_color_tx_foreground_ = new_color;
|
|
ui_->rxForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_rx_background_.name())
|
|
.arg(next_color_rx_foreground_.name()));
|
|
ui_->txForegroundLabel->setStyleSheet(
|
|
QString("background: %1; color: %2")
|
|
.arg(next_color_rx_background_.name())
|
|
.arg(next_color_tx_foreground_.name()));
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_txFontButton_clicked() {
|
|
next_tx_text_font_ =
|
|
QFontDialog::getFont(0, next_tx_text_font_, this, tr("Font Chooser")
|
|
#if QT_VERSION >= 0x050201
|
|
,
|
|
QFontDialog::DontUseNativeDialog
|
|
#endif
|
|
);
|
|
|
|
ui_->txFontButton->setText(QString("Font (%1 %2)")
|
|
.arg(next_tx_text_font_.family())
|
|
.arg(next_tx_text_font_.pointSize()));
|
|
}
|
|
|
|
void Configuration::impl::on_composeFontButton_clicked() {
|
|
next_compose_text_font_ = QFontDialog::getFont(
|
|
0, next_compose_text_font_, this, tr("Font Chooser")
|
|
#if QT_VERSION >= 0x050201
|
|
,
|
|
QFontDialog::DontUseNativeDialog
|
|
#endif
|
|
);
|
|
ui_->composeFontButton->setText(
|
|
QString("Font (%1 %2)")
|
|
.arg(next_compose_text_font_.family())
|
|
.arg(next_compose_text_font_.pointSize()));
|
|
}
|
|
|
|
void Configuration::impl::on_pillRecipientBgButton_clicked() {
|
|
choosePillPreviewColor(next_color_pill_recipient_bg_,
|
|
ui_->pillRecipientLabel,
|
|
next_color_pill_recipient_bg_,
|
|
next_color_pill_recipient_fg_,
|
|
QStringLiteral("Recipient Pill Background"));
|
|
}
|
|
|
|
void Configuration::impl::on_pillRecipientFgButton_clicked() {
|
|
choosePillPreviewColor(next_color_pill_recipient_fg_,
|
|
ui_->pillRecipientLabel,
|
|
next_color_pill_recipient_bg_,
|
|
next_color_pill_recipient_fg_,
|
|
QStringLiteral("Recipient Pill Font"));
|
|
}
|
|
|
|
void Configuration::impl::on_pillCommandBgButton_clicked() {
|
|
choosePillPreviewColor(next_color_pill_command_bg_, ui_->pillCommandLabel,
|
|
next_color_pill_command_bg_,
|
|
next_color_pill_command_fg_,
|
|
QStringLiteral("Command Pill Background"));
|
|
}
|
|
|
|
void Configuration::impl::on_pillCommandFgButton_clicked() {
|
|
choosePillPreviewColor(next_color_pill_command_fg_, ui_->pillCommandLabel,
|
|
next_color_pill_command_bg_,
|
|
next_color_pill_command_fg_,
|
|
QStringLiteral("Command Pill Font"));
|
|
}
|
|
|
|
void Configuration::impl::on_pillGroupBgButton_clicked() {
|
|
choosePillPreviewColor(next_color_pill_group_bg_, ui_->pillGroupLabel,
|
|
next_color_pill_group_bg_,
|
|
next_color_pill_group_fg_,
|
|
QStringLiteral("Group Pill Background"));
|
|
}
|
|
|
|
void Configuration::impl::on_pillGroupFgButton_clicked() {
|
|
choosePillPreviewColor(next_color_pill_group_fg_, ui_->pillGroupLabel,
|
|
next_color_pill_group_bg_,
|
|
next_color_pill_group_fg_,
|
|
QStringLiteral("Group Pill Font"));
|
|
}
|
|
|
|
void Configuration::impl::on_pillSenderBgButton_clicked() {
|
|
choosePillPreviewColor(next_color_pill_sender_bg_, ui_->pillSenderLabel,
|
|
next_color_pill_sender_bg_,
|
|
next_color_pill_sender_fg_,
|
|
QStringLiteral("Sender Pill Background"));
|
|
}
|
|
|
|
void Configuration::impl::on_pillSenderFgButton_clicked() {
|
|
choosePillPreviewColor(next_color_pill_sender_fg_, ui_->pillSenderLabel,
|
|
next_color_pill_sender_bg_,
|
|
next_color_pill_sender_fg_,
|
|
QStringLiteral("Sender Pill Font"));
|
|
}
|
|
|
|
void Configuration::impl::on_PTT_port_combo_box_activated(int /* index */) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_CAT_port_combo_box_activated(int /* index */) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_CAT_serial_baud_combo_box_currentIndexChanged(
|
|
int /* index */) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_CAT_handshake_button_group_buttonClicked(
|
|
QAbstractButton *) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_rig_combo_box_currentIndexChanged(
|
|
int /* index */) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_CAT_data_bits_button_group_buttonClicked(
|
|
QAbstractButton *) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_CAT_stop_bits_button_group_buttonClicked(
|
|
QAbstractButton *) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_CAT_poll_interval_spin_box_valueChanged(
|
|
int /* value */) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_split_mode_button_group_buttonClicked(
|
|
QAbstractButton *) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_test_CAT_push_button_clicked() {
|
|
if (!validate()) {
|
|
return;
|
|
}
|
|
|
|
ui_->test_CAT_push_button->setStyleSheet({});
|
|
if (open_rig(true)) {
|
|
// Q_EMIT sync (true);
|
|
}
|
|
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_test_PTT_push_button_clicked(bool checked) {
|
|
ui_->test_PTT_push_button->setChecked(!checked); // let status
|
|
// update check us
|
|
if (!validate()) {
|
|
return;
|
|
}
|
|
|
|
if (rig_active_) {
|
|
// Update the data mode from the UI before testing PTT
|
|
data_mode_ =
|
|
static_cast<DataMode>(ui_->TX_mode_button_group->checkedId());
|
|
Q_EMIT self_->transceiver_ptt(checked);
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_force_DTR_combo_box_currentIndexChanged(
|
|
int /* index */) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_force_RTS_combo_box_currentIndexChanged(
|
|
int /* index */) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_PTT_method_button_group_buttonClicked(
|
|
QAbstractButton *) {
|
|
set_rig_invariants();
|
|
}
|
|
|
|
void Configuration::impl::on_groups_line_edit_textChanged(QString const &) {}
|
|
|
|
void Configuration::impl::on_info_message_line_edit_textChanged(
|
|
QString const &) {}
|
|
|
|
void Configuration::impl::on_cq_message_line_edit_textChanged(QString const &) {
|
|
}
|
|
|
|
void Configuration::impl::on_reply_message_line_edit_textChanged(
|
|
QString const &) {}
|
|
|
|
void Configuration::impl::on_add_macro_line_edit_editingFinished() {
|
|
ui_->add_macro_line_edit->setText(
|
|
ui_->add_macro_line_edit->text().toUpper());
|
|
}
|
|
|
|
void Configuration::impl::on_delete_macro_push_button_clicked(
|
|
bool /* checked */) {
|
|
auto selection_model = ui_->macros_list_view->selectionModel();
|
|
if (selection_model->hasSelection()) {
|
|
// delete all selected items
|
|
delete_selected_macros(selection_model->selectedRows());
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::delete_macro() {
|
|
auto selection_model = ui_->macros_list_view->selectionModel();
|
|
if (!selection_model->hasSelection()) {
|
|
// delete item under cursor if any
|
|
auto index = selection_model->currentIndex();
|
|
if (index.isValid()) {
|
|
next_macros_.removeRow(index.row());
|
|
}
|
|
} else {
|
|
// delete the whole selection
|
|
delete_selected_macros(selection_model->selectedRows());
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::delete_selected_macros(
|
|
QModelIndexList selected_rows) {
|
|
// sort in reverse row order so that we can delete without changing
|
|
// indices underneath us
|
|
std::sort(selected_rows.begin(), selected_rows.end(),
|
|
[](QModelIndex const &lhs, QModelIndex const &rhs) {
|
|
return rhs.row() < lhs.row(); // reverse row ordering
|
|
});
|
|
|
|
// now delete them
|
|
Q_FOREACH (auto index, selected_rows) {
|
|
next_macros_.removeRow(index.row());
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_add_macro_push_button_clicked(bool /* checked */) {
|
|
if (next_macros_.insertRow(next_macros_.rowCount())) {
|
|
auto index = next_macros_.index(next_macros_.rowCount() - 1);
|
|
ui_->macros_list_view->setCurrentIndex(index);
|
|
next_macros_.setData(index, ui_->add_macro_line_edit->text().toUpper());
|
|
ui_->add_macro_line_edit->clear();
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::delete_frequencies() {
|
|
auto selection_model = ui_->frequencies_table_view->selectionModel();
|
|
selection_model->select(selection_model->selection(),
|
|
QItemSelectionModel::SelectCurrent |
|
|
QItemSelectionModel::Rows);
|
|
next_frequencies_.removeDisjointRows(selection_model->selectedRows());
|
|
ui_->frequencies_table_view->resizeColumnToContents(
|
|
FrequencyList_v3::mode_column);
|
|
}
|
|
|
|
void Configuration::impl::load_frequencies() {
|
|
auto file_name = QFileDialog::getOpenFileName(
|
|
this, tr("Load Working Frequencies"),
|
|
writeable_data_dir_.absolutePath(),
|
|
tr("Frequency files (*.qrg);;All files (*.*)"));
|
|
if (!file_name.isNull()) {
|
|
auto const list = read_frequencies_file(file_name);
|
|
if (list.size() &&
|
|
(!next_frequencies_.frequency_list().size() ||
|
|
JS8MessageBox::Yes ==
|
|
JS8MessageBox::query_message(
|
|
this, tr("Replace Working Frequencies"),
|
|
tr("Are you sure you want to discard your current "
|
|
"working frequencies and replace them with the "
|
|
"loaded ones?")))) {
|
|
next_frequencies_.frequency_list(list); // update the model
|
|
}
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::merge_frequencies() {
|
|
auto file_name = QFileDialog::getOpenFileName(
|
|
this, tr("Merge Working Frequencies"),
|
|
writeable_data_dir_.absolutePath(),
|
|
tr("Frequency files (*.qrg);;All files (*.*)"));
|
|
if (!file_name.isNull()) {
|
|
next_frequencies_.frequency_list_merge(
|
|
read_frequencies_file(file_name)); // update the model
|
|
}
|
|
}
|
|
|
|
FrequencyList_v3::FrequencyItems
|
|
Configuration::impl::read_frequencies_file(QString const &file_name) {
|
|
FrequencyList_v3::FrequencyItems list;
|
|
QFile frequencies_file{file_name};
|
|
bool opened = frequencies_file.open(QFile::ReadOnly);
|
|
|
|
if (!opened) {
|
|
JS8MessageBox::warning_message(this, tr("Unable to open frequencies file"));
|
|
return list;
|
|
}
|
|
|
|
QDataStream ids{&frequencies_file};
|
|
|
|
quint32 magic;
|
|
ids >> magic;
|
|
if (qrg_magic != magic) {
|
|
JS8MessageBox::warning_message(this, tr("Not a valid frequencies file"),
|
|
tr("Incorrect file magic"));
|
|
return list;
|
|
}
|
|
quint32 version;
|
|
ids >> version;
|
|
// handle version checks and QDataStream version here if
|
|
// necessary
|
|
if (version > qrg_version) {
|
|
JS8MessageBox::warning_message(this, tr("Not a valid frequencies file"),
|
|
tr("Version is too new"));
|
|
return list;
|
|
}
|
|
|
|
// de-serialize the data using version if necessary to
|
|
// handle old schemata
|
|
if (version == 102) {
|
|
FrequencyList_v2::FrequencyItems v2list;
|
|
ids >> v2list;
|
|
|
|
list.reserve(v2list.size());
|
|
for (const auto& it : v2list) {
|
|
FrequencyList_v3::Item n;
|
|
n.frequency_ = it.frequency_;
|
|
n.mode_ = it.mode_;
|
|
n.region_ = it.region_;
|
|
n.description_.clear();
|
|
list.push_back(std::move(n));
|
|
}
|
|
}
|
|
else {
|
|
ids >> list;
|
|
}
|
|
|
|
|
|
if (ids.status() != QDataStream::Ok || !ids.atEnd()) {
|
|
JS8MessageBox::warning_message(this, tr("Not a valid frequencies file"),
|
|
tr("Contents corrupt"));
|
|
list.clear();
|
|
return list;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
void Configuration::impl::save_frequencies() {
|
|
auto file_name = QFileDialog::getSaveFileName(
|
|
this, tr("Save Working Frequencies"),
|
|
writeable_data_dir_.absolutePath(),
|
|
tr("Frequency files (*.qrg);;All files (*.*)"));
|
|
if (!file_name.isNull()) {
|
|
QFile frequencies_file{file_name};
|
|
bool opened = frequencies_file.open(QFile::WriteOnly);
|
|
|
|
if (!opened) {
|
|
JS8MessageBox::warning_message(this, tr("Unable to open frequencies file"));
|
|
return;
|
|
}
|
|
|
|
QDataStream ods{&frequencies_file};
|
|
auto selection_model = ui_->frequencies_table_view->selectionModel();
|
|
if (selection_model->hasSelection() &&
|
|
JS8MessageBox::Yes ==
|
|
JS8MessageBox::query_message(
|
|
this, tr("Only Save Selected Working Frequencies"),
|
|
tr("Are you sure you want to save only the "
|
|
"working frequencies that are currently selected? "
|
|
"Click No to save all."))) {
|
|
selection_model->select(selection_model->selection(),
|
|
QItemSelectionModel::SelectCurrent |
|
|
QItemSelectionModel::Rows);
|
|
ods << qrg_magic << qrg_version
|
|
<< next_frequencies_.frequency_list(
|
|
selection_model->selectedRows());
|
|
} else {
|
|
ods << qrg_magic << qrg_version
|
|
<< next_frequencies_.frequency_list();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::reset_frequencies() {
|
|
if (JS8MessageBox::Yes ==
|
|
JS8MessageBox::query_message(
|
|
this, tr("Reset Working Frequencies"),
|
|
tr("Are you sure you want to discard your current "
|
|
"working frequencies and replace them with default "
|
|
"ones?"))) {
|
|
next_frequencies_.reset_to_defaults();
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::insert_frequency() {
|
|
if (QDialog::Accepted == frequency_dialog_->exec()) {
|
|
ui_->frequencies_table_view->setCurrentIndex(
|
|
next_frequencies_.add(frequency_dialog_->item()));
|
|
ui_->frequencies_table_view->resizeColumnToContents(
|
|
FrequencyList_v3::mode_column);
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::hop_to_station() {
|
|
/*
|
|
* If there is a valid selection in the station table, create a station
|
|
* instance from the row data and emit the manual_band_hop_requested signal
|
|
*/
|
|
auto selection_model = ui_->stations_table_view->selectionModel();
|
|
if (!selection_model || !selection_model->hasSelection()) {
|
|
return;
|
|
}
|
|
|
|
auto selected_rows = selection_model->selectedRows();
|
|
if (selected_rows.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QModelIndex index = selected_rows.back();
|
|
|
|
// Get the time strings
|
|
QString switch_at_str =
|
|
index.sibling(index.row(), StationList::Column::switch_at_column)
|
|
.data(Qt::EditRole)
|
|
.toString();
|
|
QString switch_until_str =
|
|
index.sibling(index.row(), StationList::Column::switch_until_column)
|
|
.data(Qt::EditRole)
|
|
.toString();
|
|
|
|
// Create QDateTime objects with today's date and the times from the model
|
|
QTime at_time = QTime::fromString(switch_at_str, "hh:mm");
|
|
QTime until_time = QTime::fromString(switch_until_str, "hh:mm");
|
|
|
|
StationList::Station station{
|
|
index.sibling(index.row(), StationList::Column::band_column)
|
|
.data(Qt::EditRole)
|
|
.toString(),
|
|
index.sibling(index.row(), StationList::Column::frequency_column)
|
|
.data(Qt::EditRole)
|
|
.value<Radio::Frequency>(),
|
|
QDateTime(QDate(2000, 1, 1), at_time, QTimeZone::utc()),
|
|
QDateTime(QDate(2000, 1, 1), until_time, QTimeZone::utc()),
|
|
index.sibling(index.row(), StationList::Column::description_column)
|
|
.data(Qt::EditRole)
|
|
.toString()};
|
|
|
|
Q_EMIT self_->manual_band_hop_requested(station);
|
|
}
|
|
|
|
void Configuration::impl::delete_stations() {
|
|
auto selection_model = ui_->stations_table_view->selectionModel();
|
|
selection_model->select(selection_model->selection(),
|
|
QItemSelectionModel::SelectCurrent |
|
|
QItemSelectionModel::Rows);
|
|
next_stations_.removeDisjointRows(selection_model->selectedRows());
|
|
ui_->stations_table_view->resizeColumnToContents(StationList::band_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::frequency_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::switch_at_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::switch_until_column);
|
|
}
|
|
|
|
void Configuration::impl::insert_station() {
|
|
if (QDialog::Accepted == station_dialog_->exec()) {
|
|
auto station = station_dialog_->station();
|
|
if (station.frequency_ == 0) {
|
|
Frequency l;
|
|
Frequency h;
|
|
if (bands_.findFreq(station.band_name_, &l, &h)) {
|
|
station.frequency_ = l;
|
|
}
|
|
}
|
|
|
|
ui_->stations_table_view->setCurrentIndex(next_stations_.add(station));
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::band_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::frequency_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::switch_at_column);
|
|
ui_->stations_table_view->resizeColumnToContents(
|
|
StationList::switch_until_column);
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_save_path_select_push_button_clicked(
|
|
bool /* checked */) {
|
|
QFileDialog fd{this, tr("Save Directory"),
|
|
ui_->save_path_display_label->text()};
|
|
fd.setFileMode(QFileDialog::Directory);
|
|
fd.setOption(QFileDialog::ShowDirsOnly);
|
|
if (fd.exec()) {
|
|
if (fd.selectedFiles().size()) {
|
|
ui_->save_path_display_label->setText(fd.selectedFiles().at(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::on_calibration_intercept_spin_box_valueChanged(
|
|
double) {
|
|
rig_active_ = false; // force reset
|
|
}
|
|
|
|
void Configuration::impl::on_calibration_slope_ppm_spin_box_valueChanged(
|
|
double) {
|
|
rig_active_ = false; // force reset
|
|
}
|
|
|
|
bool Configuration::impl::have_rig() {
|
|
if (!open_rig()) {
|
|
JS8MessageBox::critical_message(this, tr("Rig control error"),
|
|
tr("Failed to open connection to rig"));
|
|
}
|
|
return rig_active_;
|
|
}
|
|
|
|
bool Configuration::impl::open_rig(bool force) {
|
|
auto result = false;
|
|
|
|
auto const rig_data = gather_rig_data();
|
|
if (force || !rig_active_ || rig_data != saved_rig_params_) {
|
|
try {
|
|
close_rig();
|
|
|
|
// create a new Transceiver object
|
|
auto rig =
|
|
transceiver_factory_.create(rig_data, transceiver_thread_);
|
|
cached_rig_state_ = Transceiver::TransceiverState{};
|
|
|
|
// hook up Configuration transceiver control signals to Transceiver
|
|
// slots
|
|
//
|
|
// these connections cross the thread boundary
|
|
rig_connections_
|
|
<< connect(this, &Configuration::impl::set_transceiver,
|
|
rig.get(), &Transceiver::set);
|
|
|
|
// hook up Transceiver signals to Configuration signals
|
|
//
|
|
// these connections cross the thread boundary
|
|
rig_connections_ << connect(
|
|
rig.get(), &Transceiver::resolution, this,
|
|
[this](int resolution) { rig_resolution_ = resolution; });
|
|
rig_connections_
|
|
<< connect(rig.get(), &Transceiver::update, this,
|
|
&Configuration::impl::handle_transceiver_update);
|
|
rig_connections_
|
|
<< connect(rig.get(), &Transceiver::failure, this,
|
|
&Configuration::impl::handle_transceiver_failure);
|
|
|
|
// setup thread safe startup and close down semantics
|
|
rig_connections_
|
|
<< connect(this, &Configuration::impl::start_transceiver,
|
|
rig.get(), &Transceiver::start);
|
|
rig_connections_
|
|
<< connect(this, &Configuration::impl::stop_transceiver,
|
|
rig.get(), &Transceiver::stop);
|
|
|
|
auto p = rig.release(); // take ownership
|
|
|
|
// schedule destruction on thread quit
|
|
connect(transceiver_thread_, &QThread::finished, p,
|
|
&QObject::deleteLater);
|
|
|
|
// schedule eventual destruction for non-closing situations
|
|
//
|
|
// must be queued connection to avoid premature
|
|
// self-immolation since finished signal is going to be
|
|
// emitted from the object that will get destroyed in its
|
|
// own stop slot i.e. a same thread signal to slot
|
|
// connection which by default will be reduced to a method
|
|
// function call.
|
|
connect(p, &Transceiver::finished, p, &Transceiver::deleteLater,
|
|
Qt::QueuedConnection);
|
|
|
|
ui_->test_CAT_push_button->setStyleSheet({});
|
|
rig_active_ = true;
|
|
Q_EMIT start_transceiver(
|
|
++transceiver_command_number_); // start rig on its thread
|
|
result = true;
|
|
} catch (std::exception const &e) {
|
|
handle_transceiver_failure(e.what());
|
|
}
|
|
|
|
saved_rig_params_ = rig_data;
|
|
rig_changed_ = true;
|
|
} else {
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Configuration::impl::set_cached_mode() {
|
|
MODE mode{Transceiver::UNK};
|
|
// override cache mode with what we want to enforce which includes
|
|
// UNK (unknown) where we want to leave the rig mode untouched
|
|
switch (data_mode_) {
|
|
case data_mode_USB:
|
|
mode = Transceiver::USB;
|
|
break;
|
|
case data_mode_data:
|
|
mode = Transceiver::DIG_U;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cached_rig_state_.mode(mode);
|
|
}
|
|
|
|
void Configuration::impl::transceiver_frequency(Frequency f) {
|
|
cached_rig_state_.online(true); // we want the rig online
|
|
set_cached_mode();
|
|
|
|
// apply any offset & calibration
|
|
// we store the offset here for use in feedback from the rig, we
|
|
// cannot absolutely determine if the offset should apply but by
|
|
// simply picking an offset when the Rx frequency is set and
|
|
// sticking to it we get sane behaviour
|
|
cached_rig_state_.frequency(apply_calibration(f));
|
|
|
|
Q_EMIT set_transceiver(cached_rig_state_, ++transceiver_command_number_);
|
|
}
|
|
|
|
void Configuration::impl::transceiver_tx_frequency(Frequency f) {
|
|
Q_ASSERT(!f || split_mode());
|
|
if (split_mode()) {
|
|
cached_rig_state_.online(true); // we want the rig online
|
|
set_cached_mode();
|
|
cached_rig_state_.split(f);
|
|
cached_rig_state_.tx_frequency(f);
|
|
|
|
// lookup offset for tx and apply calibration
|
|
if (f) {
|
|
// apply and offset and calibration
|
|
// we store the offset here for use in feedback from the
|
|
// rig, we cannot absolutely determine if the offset should
|
|
// apply but by simply picking an offset when the Rx
|
|
// frequency is set and sticking to it we get sane behaviour
|
|
cached_rig_state_.tx_frequency(apply_calibration(f));
|
|
}
|
|
|
|
Q_EMIT set_transceiver(cached_rig_state_,
|
|
++transceiver_command_number_);
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::transceiver_mode(MODE m) {
|
|
cached_rig_state_.online(true); // we want the rig online
|
|
cached_rig_state_.mode(m);
|
|
Q_EMIT set_transceiver(cached_rig_state_, ++transceiver_command_number_);
|
|
}
|
|
|
|
void Configuration::impl::transceiver_ptt(bool on) {
|
|
cached_rig_state_.online(true); // we want the rig online
|
|
set_cached_mode();
|
|
cached_rig_state_.ptt(on);
|
|
Q_EMIT set_transceiver(cached_rig_state_, ++transceiver_command_number_);
|
|
}
|
|
|
|
void Configuration::impl::sync_transceiver(bool /*force_signal*/) {
|
|
// pass this on as cache must be ignored
|
|
// Q_EMIT sync (force_signal);
|
|
}
|
|
|
|
void Configuration::impl::handle_transceiver_update(
|
|
TransceiverState const &state, unsigned sequence_number) {
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::handle_transceiver_update: Transceiver State #:"
|
|
<< sequence_number << state;
|
|
|
|
// only follow rig on some information, ignore other stuff
|
|
cached_rig_state_.online(state.online());
|
|
cached_rig_state_.frequency(state.frequency());
|
|
cached_rig_state_.mode(state.mode());
|
|
cached_rig_state_.split(state.split());
|
|
|
|
if (state.online()) {
|
|
ui_->test_PTT_push_button->setChecked(state.ptt());
|
|
|
|
if (isVisible()) {
|
|
ui_->test_CAT_push_button->setStyleSheet(
|
|
"QPushButton {background-color: green;}");
|
|
|
|
auto const &rig = ui_->rig_combo_box->currentText();
|
|
auto ptt_method = static_cast<TransceiverFactory::PTTMethod>(
|
|
ui_->PTT_method_button_group->checkedId());
|
|
auto CAT_PTT_enabled = transceiver_factory_.has_CAT_PTT(rig);
|
|
ui_->test_PTT_push_button->setEnabled(
|
|
(TransceiverFactory::PTT_method_CAT == ptt_method &&
|
|
CAT_PTT_enabled) ||
|
|
TransceiverFactory::PTT_method_DTR == ptt_method ||
|
|
TransceiverFactory::PTT_method_RTS == ptt_method);
|
|
}
|
|
} else {
|
|
close_rig();
|
|
}
|
|
|
|
// pass on to clients if current command is processed
|
|
if (sequence_number == transceiver_command_number_) {
|
|
TransceiverState reported_state{state};
|
|
// take off calibration & offset
|
|
reported_state.frequency(
|
|
remove_calibration(reported_state.frequency()));
|
|
|
|
if (reported_state.tx_frequency()) {
|
|
// take off calibration & offset
|
|
reported_state.tx_frequency(
|
|
remove_calibration(reported_state.tx_frequency()));
|
|
}
|
|
|
|
Q_EMIT self_->transceiver_update(reported_state);
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::handle_transceiver_failure(QString const &reason) {
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::handle_transceiver_failure: reason:" << reason;
|
|
close_rig();
|
|
ui_->test_PTT_push_button->setChecked(false);
|
|
|
|
if (isVisible()) {
|
|
JS8MessageBox::critical_message(this, tr("Rig failure"), reason);
|
|
} else {
|
|
// pass on if our dialog isn't active
|
|
Q_EMIT self_->transceiver_failure(reason);
|
|
}
|
|
}
|
|
|
|
void Configuration::impl::close_rig() {
|
|
ui_->test_PTT_push_button->setEnabled(false);
|
|
|
|
// revert to no rig configured
|
|
if (rig_active_) {
|
|
ui_->test_CAT_push_button->setStyleSheet(
|
|
"QPushButton {background-color: red;}");
|
|
Q_EMIT stop_transceiver();
|
|
for (auto const &connection : rig_connections_) {
|
|
disconnect(connection);
|
|
}
|
|
rig_connections_.clear();
|
|
rig_active_ = false;
|
|
}
|
|
}
|
|
|
|
// find the audio device that matches the specified name, also
|
|
// populate into the selection combo box with any devices we find in
|
|
// the search
|
|
|
|
QAudioDevice Configuration::impl::find_audio_device(QAudioDevice::Mode const mode,
|
|
QComboBox *const combo_box,
|
|
QString const &saved_id,
|
|
QString const &saved_name,
|
|
bool const allow_monitors)
|
|
{
|
|
Q_EMIT self_->enumerating_audio_devices();
|
|
|
|
auto const &devices = (mode == QAudioDevice::Input)
|
|
? QMediaDevices::audioInputs()
|
|
: QMediaDevices::audioOutputs();
|
|
|
|
// ---- 1) Exact ID match (preferred, stable, no collisions)
|
|
if (!saved_id.isEmpty()) {
|
|
for (auto const &p : devices) {
|
|
if (p.id() == saved_id) {
|
|
combo_box->insertItem(0, p.description(), QVariant::fromValue(p));
|
|
combo_box->setCurrentIndex(0);
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- 2) Legacy: match by description, but choose deterministically
|
|
if (!saved_name.isEmpty()) {
|
|
QList<QAudioDevice> matches;
|
|
matches.reserve(devices.size());
|
|
|
|
for (auto const &p : devices) {
|
|
if (p.description() != saved_name)
|
|
continue;
|
|
|
|
#ifdef Q_OS_LINUX
|
|
if (!allow_monitors && is_monitor(p))
|
|
continue;
|
|
#endif
|
|
matches.push_back(p);
|
|
}
|
|
|
|
auto best = QAudioDevice{};
|
|
int bestScore = std::numeric_limits<int>::min();
|
|
|
|
for (auto const &p : matches) {
|
|
int score = 0;
|
|
|
|
#ifdef Q_OS_LINUX
|
|
if (is_monitor(p)) score -= 1000; // only reachable if allow_monitors==true
|
|
if (looks_like_capture(p)) score += 200; // for Input restores
|
|
#endif
|
|
// Soft hint: don't gate on mode, just prefer it when truthful
|
|
if (p.mode() == mode) score += 10;
|
|
else score -= 10;
|
|
|
|
// Prefer stable-looking IDs over empty/odd ones
|
|
if (!p.id().isEmpty()) score += 1;
|
|
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
best = p;
|
|
}
|
|
}
|
|
|
|
if (!best.isNull()) {
|
|
combo_box->insertItem(0, best.description(), QVariant::fromValue(best));
|
|
combo_box->setCurrentIndex(0);
|
|
return best;
|
|
}
|
|
|
|
// Not found (or filtered out as monitor)
|
|
combo_box->insertItem(0,
|
|
saved_name + " (" + tr("Not found", "audio device missing") + ")",
|
|
QVariant::fromValue(QAudioDevice{}));
|
|
combo_box->setCurrentIndex(0);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
// load the available audio devices into the selection combo box and
|
|
// select the default device if the current device isn't set or isn't
|
|
// available
|
|
|
|
void Configuration::impl::load_audio_devices(QAudioDevice::Mode const mode,
|
|
QComboBox *combo_box,
|
|
QAudioDevice *device,
|
|
bool const show_monitors)
|
|
{
|
|
combo_box->clear();
|
|
Q_EMIT self_->enumerating_audio_devices();
|
|
|
|
int current_index = -1;
|
|
|
|
auto const &devices = (mode == QAudioDevice::Input)
|
|
? QMediaDevices::audioInputs()
|
|
: QMediaDevices::audioOutputs();
|
|
|
|
// Count duplicate descriptions
|
|
QHash<QString, int> descCounts;
|
|
descCounts.reserve(devices.size());
|
|
for (auto const &p : devices) {
|
|
#ifdef Q_OS_LINUX
|
|
if (mode == QAudioDevice::Input && !show_monitors && is_monitor(p))
|
|
continue;
|
|
#endif
|
|
descCounts[p.description()] += 1;
|
|
}
|
|
|
|
auto const wanted_id = (device && !device->isNull()) ? device->id() : QString{};
|
|
|
|
for (auto const &p : devices) {
|
|
#ifdef Q_OS_LINUX
|
|
if (mode == QAudioDevice::Input && !show_monitors && is_monitor(p))
|
|
continue;
|
|
#endif
|
|
|
|
qCDebug(configuration_js8)
|
|
<< "Configuration::impl::load_audio_devices" << Qt::endl
|
|
<< " id:" << p.id() << Qt::endl
|
|
<< " name:" << p.description() << Qt::endl
|
|
<< " mode:" << p.mode() << Qt::endl
|
|
<< " channelConfiguration:" << p.channelConfiguration()
|
|
<< Qt::endl
|
|
<< " supportedSampleFormats:" << p.supportedSampleFormats()
|
|
<< Qt::endl
|
|
<< " preferredFormat:" << p.preferredFormat();
|
|
|
|
auto const formats = p.supportedSampleFormats();
|
|
|
|
// Filter out devices that do not support PCM sample formats.
|
|
if (!std::any_of(formats.begin(), formats.end(), [](auto const format) {
|
|
return (format == QAudioFormat::SampleFormat::Int16 ||
|
|
format == QAudioFormat::SampleFormat::Int32 ||
|
|
format == QAudioFormat::SampleFormat::Float);
|
|
})) {
|
|
continue;
|
|
}
|
|
|
|
auto const base = p.description();
|
|
auto const label = make_label(base, p, descCounts.value(base, 1));
|
|
|
|
combo_box->addItem(label, QVariant::fromValue(p));
|
|
|
|
if (!wanted_id.isEmpty() && p.id() == wanted_id) {
|
|
current_index = combo_box->count() - 1;
|
|
}
|
|
}
|
|
|
|
// Make the UX smoother by picking a fallback
|
|
if (current_index < 0 && combo_box->count() > 0) {
|
|
current_index = 0;
|
|
}
|
|
|
|
combo_box->setCurrentIndex(current_index);
|
|
}
|
|
|
|
// Enable only the channels that are supported by the selected audio device.
|
|
// The function above will have provided the device channel configuration,
|
|
// which is a QAudioFormat::ChannelConfig enumeration, as the item data of
|
|
// the source combo box.
|
|
//
|
|
// - If the channel configuration isn't unknown, then the device is usable,
|
|
// perhaps only with a monaural channel, but at least usable.
|
|
//
|
|
// - If the device is usable, and has a configuration something other than
|
|
// a monaural channel, then we're at least looking at a stereo device.
|
|
// The device might have many more channels that just a left and right,
|
|
// but it's at least stereo.
|
|
|
|
void Configuration::impl::update_audio_channels(
|
|
QComboBox const *const source_combo_box,
|
|
QComboBox const *combo_box,
|
|
int const index,
|
|
bool const allow_both)
|
|
{
|
|
auto dev = source_combo_box->itemData(index).value<QAudioDevice>();
|
|
|
|
int ch = 0;
|
|
if (!dev.isNull())
|
|
ch = dev.preferredFormat().channelCount();
|
|
|
|
const auto cfg = dev.channelConfiguration();
|
|
|
|
bool usable = false;
|
|
bool mono = false;
|
|
bool stereo = false;
|
|
|
|
if (ch > 0) {
|
|
usable = true;
|
|
mono = (ch == 1);
|
|
stereo = (ch >= 2);
|
|
} else {
|
|
// fallback for platforms/backends where preferredFormat isn't informative
|
|
usable = (cfg != QAudioFormat::ChannelConfigUnknown);
|
|
mono = usable && (cfg == QAudioFormat::ChannelConfigMono);
|
|
stereo = usable && (cfg != QAudioFormat::ChannelConfigMono);
|
|
}
|
|
|
|
auto model = qobject_cast<QStandardItemModel *>(combo_box->model());
|
|
if (!model) return;
|
|
|
|
// If unknown, leaving Mono enabled avoids "everything disabled" UX.
|
|
model->item(AudioDevice::Mono)->setEnabled(mono || !usable);
|
|
model->item(AudioDevice::Left)->setEnabled(stereo);
|
|
model->item(AudioDevice::Right)->setEnabled(stereo);
|
|
model->item(AudioDevice::Both)->setEnabled(stereo && allow_both);
|
|
}
|
|
|
|
void Configuration::impl::find_tab(QWidget *target) {
|
|
for (auto const *parent = target->parentWidget(); parent;
|
|
parent = parent->parentWidget()) {
|
|
if (auto const index = ui_->configuration_tabs->indexOf(parent);
|
|
index != -1) {
|
|
ui_->configuration_tabs->setCurrentIndex(index);
|
|
break;
|
|
}
|
|
}
|
|
target->setFocus();
|
|
}
|
|
|
|
// load all the supported rig names into the selection combo box
|
|
void Configuration::impl::enumerate_rigs() {
|
|
ui_->rig_combo_box->clear();
|
|
|
|
auto rigs = transceiver_factory_.supported_transceivers();
|
|
|
|
for (auto r = rigs.cbegin(); r != rigs.cend(); ++r) {
|
|
if ("None" == r.key()) {
|
|
// put None first
|
|
ui_->rig_combo_box->insertItem(0, r.key(), r.value().model_number_);
|
|
} else {
|
|
int i;
|
|
for (i = 1; i < ui_->rig_combo_box->count() &&
|
|
(r.key().toLower() >
|
|
ui_->rig_combo_box->itemText(i).toLower());
|
|
++i)
|
|
;
|
|
if (i < ui_->rig_combo_box->count())
|
|
ui_->rig_combo_box->insertItem(i, r.key(),
|
|
r.value().model_number_);
|
|
else
|
|
ui_->rig_combo_box->addItem(r.key(), r.value().model_number_);
|
|
}
|
|
}
|
|
|
|
ui_->rig_combo_box->setCurrentText(rig_params_.rig_name);
|
|
}
|
|
|
|
void Configuration::impl::fill_port_combo_box(QComboBox *cb) {
|
|
auto current_text = cb->currentText();
|
|
cb->clear();
|
|
Q_FOREACH (auto const &p, QSerialPortInfo::availablePorts()) {
|
|
if (!p.portName().contains("NULL")) // virtual serial port pairs
|
|
{
|
|
// remove possibly confusing Windows device path (OK because
|
|
// it gets added back by Hamlib)
|
|
cb->addItem(
|
|
p.systemLocation().remove(QRegularExpression{R"(^\\\\\.\\)"}));
|
|
}
|
|
}
|
|
cb->addItem("USB");
|
|
cb->setEditText(current_text);
|
|
}
|
|
|
|
auto Configuration::impl::apply_calibration(Frequency f) const -> Frequency {
|
|
if (frequency_calibration_disabled_)
|
|
return f;
|
|
return std::llround(calibration_.intercept +
|
|
(1. + calibration_.slope_ppm / 1.e6) * f);
|
|
}
|
|
|
|
auto Configuration::impl::remove_calibration(Frequency f) const -> Frequency {
|
|
if (frequency_calibration_disabled_)
|
|
return f;
|
|
return std::llround((f - calibration_.intercept) /
|
|
(1. + calibration_.slope_ppm / 1.e6));
|
|
}
|
|
|
|
// load the available network interfaces into the selection combo box
|
|
void Configuration::impl::load_network_interfaces(
|
|
CheckableItemComboBox *combo_box, QStringList current) {
|
|
combo_box->clear();
|
|
for (auto const &net_if : QNetworkInterface::allInterfaces()) {
|
|
auto flags = QNetworkInterface::IsUp | QNetworkInterface::CanMulticast;
|
|
if ((net_if.flags() & flags) == flags) {
|
|
bool check_it = current.contains(net_if.name());
|
|
if (net_if.flags() & QNetworkInterface::IsLoopBack) {
|
|
wsjtx_loopback_interface_name_ = net_if.name();
|
|
if (!current.size()) {
|
|
check_it = true;
|
|
}
|
|
}
|
|
auto item = combo_box->addCheckItem(
|
|
net_if.humanReadableName(), net_if.name(),
|
|
check_it ? Qt::Checked : Qt::Unchecked);
|
|
auto tip =
|
|
QString{"name(index): %1(%2) - %3"}
|
|
.arg(net_if.name())
|
|
.arg(net_if.index())
|
|
.arg(net_if.flags() & QNetworkInterface::IsUp ? "Up"
|
|
: "Down");
|
|
auto hw_addr = net_if.hardwareAddress();
|
|
if (hw_addr.size()) {
|
|
tip += QString{"\nhw: %1"}.arg(net_if.hardwareAddress());
|
|
}
|
|
auto aes = net_if.addressEntries();
|
|
if (aes.size()) {
|
|
tip += "\naddresses:";
|
|
for (auto const &ae : aes) {
|
|
tip += QString{"\n ip: %1/%2"}
|
|
.arg(ae.ip().toString())
|
|
.arg(ae.prefixLength());
|
|
}
|
|
}
|
|
item->setToolTip(tip);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Attaches a trailing warning icon that shows when `isInvalid()` returns true.
|
|
// Returns the QAction* in case you want to tweak tooltip/icon later.
|
|
QAction *Configuration::impl::attachRequiredIndicator(QLineEdit *edit,
|
|
const QString &toolTip) {
|
|
Q_ASSERT(edit);
|
|
|
|
QAction *warn = edit->addAction(
|
|
edit->style()->standardIcon(QStyle::SP_MessageBoxWarning),
|
|
QLineEdit::TrailingPosition);
|
|
|
|
warn->setToolTip(toolTip);
|
|
warn->setVisible(false);
|
|
|
|
auto update = [edit, warn]() {
|
|
const bool invalid = edit->text().trimmed().isEmpty();
|
|
warn->setVisible(invalid);
|
|
};
|
|
|
|
connect(edit, &QLineEdit::textChanged, edit,
|
|
[update](const QString &) { update(); });
|
|
connect(edit, &QLineEdit::editingFinished, edit, update);
|
|
|
|
update();
|
|
return warn;
|
|
}
|
|
|
|
// get the selected network interfaces from the selection combo box
|
|
QStringList Configuration::impl::get_selected_network_interfaces(
|
|
CheckableItemComboBox *combo_box) {
|
|
QStringList interfaces;
|
|
auto model = static_cast<QStandardItemModel *>(combo_box->model());
|
|
for (int row = 0; row < model->rowCount(); ++row) {
|
|
if (Qt::Checked == model->item(row)->checkState()) {
|
|
interfaces << model->item(row)->data().toString();
|
|
}
|
|
}
|
|
return interfaces;
|
|
}
|
|
|
|
inline QString Configuration::impl::dev_id(QAudioDevice const& d) {
|
|
return d.isNull() ? QString{} : d.id();
|
|
}
|
|
|
|
inline bool Configuration::impl::is_monitor(QAudioDevice const& d) {
|
|
#ifdef Q_OS_LINUX
|
|
return !d.isNull() && d.id().endsWith(".monitor");
|
|
#else
|
|
Q_UNUSED(d);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline bool Configuration::impl::looks_like_capture(QAudioDevice const& d) {
|
|
#ifdef Q_OS_LINUX
|
|
if (d.isNull()) return false;
|
|
const auto id = d.id();
|
|
return id.startsWith("alsa_input.") ||
|
|
id.startsWith("bluez_input.") ||
|
|
id.startsWith("pipewire_input.") ||
|
|
id.startsWith("jack_input.");
|
|
#else
|
|
Q_UNUSED(d);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// tail after last '.' is usually short: analog-stereo / iec958-stereo / monitor
|
|
inline QString Configuration::impl::id_tail(QAudioDevice const& d) {
|
|
if (d.isNull()) return {};
|
|
auto const parts = d.id().split('.');
|
|
return parts.isEmpty() ? d.id() : parts.last();
|
|
}
|
|
|
|
inline QString Configuration::impl::make_label(QString const& base, QAudioDevice const& d, int dupCount) {
|
|
if (dupCount <= 1)
|
|
return base;
|
|
|
|
#ifdef Q_OS_LINUX
|
|
if (is_monitor(d))
|
|
return base + " (Monitor)";
|
|
#endif
|
|
|
|
auto tail = id_tail(d);
|
|
if (!tail.isEmpty())
|
|
return base + " (" + tail + ")";
|
|
|
|
return base;
|
|
}
|
|
|
|
void Configuration::impl::run_diagnostics()
|
|
{
|
|
ui_->run_diagnostics_button->setEnabled(false);
|
|
ui_->copy_diagnostics_button->setEnabled(false);
|
|
QGuiApplication::setOverrideCursor(QCursor{Qt::WaitCursor});
|
|
|
|
const auto text = build_diagnostics_text();
|
|
ui_->diagnostics_text->setPlainText(text);
|
|
ui_->diagnostics_text->moveCursor(QTextCursor::Start);
|
|
|
|
QGuiApplication::restoreOverrideCursor();
|
|
ui_->run_diagnostics_button->setEnabled(true);
|
|
ui_->copy_diagnostics_button->setEnabled(true);
|
|
}
|
|
|
|
QString Configuration::impl::build_diagnostics_text() const
|
|
{
|
|
QString s;
|
|
QTextStream ts(&s);
|
|
|
|
ts << "JS8Call Diagnostics\n";
|
|
ts << "Timestamp: " << QDateTime::currentDateTime().toString(Qt::ISODate) << "\n\n";
|
|
|
|
ts << "Application\n";
|
|
ts << " App name: " << QCoreApplication::applicationName() << "\n";
|
|
ts << " Org name: " << QCoreApplication::organizationName() << "\n";
|
|
ts << " Org domain: " << QCoreApplication::organizationDomain() << "\n";
|
|
ts << " Config file: " << settings_->fileName() << "\n";
|
|
ts << " Config dir: " << QFileInfo(settings_->fileName()).absolutePath() << "\n";
|
|
ts << " Data directory: " << writeable_data_dir_.absolutePath() << "\n\n";
|
|
|
|
ts << "System\n";
|
|
ts << " Qt: " << qVersion() << "\n";
|
|
ts << " OS: " << QSysInfo::prettyProductName() << "\n";
|
|
ts << " Kernel: " << QSysInfo::kernelType() << " " << QSysInfo::kernelVersion() << "\n";
|
|
ts << " CPU arch: " << QSysInfo::currentCpuArchitecture() << "\n";
|
|
ts << " AppImage: " << qEnvironmentVariable("APPIMAGE") << "\n\n";
|
|
|
|
ts << "Qt Plugin Paths\n";
|
|
ts << " PluginsPath: " << QLibraryInfo::path(QLibraryInfo::PluginsPath) << "\n";
|
|
ts << " LibraryPath: " << QLibraryInfo::path(QLibraryInfo::LibrariesPath) << "\n";
|
|
ts << " BinariesPath: " << QLibraryInfo::path(QLibraryInfo::BinariesPath) << "\n\n";
|
|
|
|
ts << "Qt Runtime Plugin Search Paths\n";
|
|
for (auto const& p : QCoreApplication::libraryPaths()) {
|
|
ts << " " << p << "\n";
|
|
}
|
|
ts << "\n";
|
|
|
|
ts << "Environment\n";
|
|
ts << " Qt / Cross-platform\n";
|
|
append_env(ts, "QT_PLUGIN_PATH");
|
|
append_env(ts, "QT_QPA_PLATFORM_PLUGIN_PATH");
|
|
append_env(ts, "QT_QPA_PLATFORM");
|
|
append_env(ts, "QT_DEBUG_PLUGINS");
|
|
append_env(ts, "QT_LOGGING_RULES");
|
|
append_env(ts, "QT_AUDIO_BACKEND");
|
|
ts << "\n";
|
|
|
|
#ifdef Q_OS_LINUX
|
|
ts << " Linux Audio\n";
|
|
append_env(ts, "PIPEWIRE_REMOTE");
|
|
append_env(ts, "PULSE_SERVER");
|
|
append_env(ts, "XDG_RUNTIME_DIR");
|
|
#endif
|
|
|
|
#ifdef Q_OS_WIN
|
|
ts << " Windows\n";
|
|
append_env(ts, "PATH");
|
|
#endif
|
|
|
|
ts << "\n";
|
|
|
|
auto const defaultIn = QMediaDevices::defaultAudioInput();
|
|
auto const defaultOut = QMediaDevices::defaultAudioOutput();
|
|
|
|
ts << "Qt Defaults\n";
|
|
ts << " Default input: "
|
|
<< (defaultIn.isNull() ? "<none>" : defaultIn.description() + " | " + defaultIn.id()) << "\n";
|
|
ts << " Default output: "
|
|
<< (defaultOut.isNull() ? "<none>" : defaultOut.description() + " | " + defaultOut.id()) << "\n\n";
|
|
|
|
auto selected = [](QComboBox* cb) -> QAudioDevice {
|
|
return cb ? cb->currentData().value<QAudioDevice>() : QAudioDevice{};
|
|
};
|
|
|
|
auto selIn = selected(ui_->sound_input_combo_box);
|
|
auto selOut = selected(ui_->sound_output_combo_box);
|
|
auto selNtf = selected(ui_->notification_sound_output_combo_box);
|
|
|
|
ts << "Selected (from dialog)\n";
|
|
ts << " Input: " << (selIn.isNull() ? "<none>" : (selIn.description() + " | " + selIn.id())) << "\n";
|
|
ts << " Output: " << (selOut.isNull() ? "<none>" : (selOut.description() + " | " + selOut.id())) << "\n";
|
|
ts << " Notification: " << (selNtf.isNull() ? "<none>" : (selNtf.description() + " | " + selNtf.id())) << "\n\n";
|
|
|
|
ts << dump_devices("QMediaDevices::audioInputs()", QMediaDevices::audioInputs());
|
|
ts << dump_devices("QMediaDevices::audioOutputs()", QMediaDevices::audioOutputs());
|
|
|
|
#if defined(Q_OS_LINUX)
|
|
ts << "System Audio (best effort)\n\n";
|
|
ts << "pactl list sources short:\n";
|
|
ts << run_cmd("pactl", {"list", "sources", "short"}, 1200) << "\n";
|
|
|
|
ts << "pactl list sinks short:\n";
|
|
ts << run_cmd("pactl", {"list", "sinks", "short"}, 1200) << "\n";
|
|
|
|
ts << "wpctl status:\n";
|
|
ts << run_cmd("wpctl", {"status"}, 1500) << "\n";
|
|
|
|
ts << "pactl info:\n";
|
|
ts << run_cmd("pactl", {"info"}, 1200) << "\n";
|
|
|
|
|
|
#elif defined(Q_OS_WIN)
|
|
ts << "Windows Audio (best effort)\n\n";
|
|
|
|
ts << "PowerShell Win32_SoundDevice:\n";
|
|
ts << run_cmd("powershell",
|
|
{"-NoProfile", "-Command",
|
|
"Get-CimInstance Win32_SoundDevice | Format-List Name,Manufacturer,Status,PNPDeviceID"},
|
|
2500) << "\n";
|
|
|
|
ts << "PowerShell AudioEndpoint:\n";
|
|
ts << run_cmd("powershell",
|
|
{"-NoProfile", "-Command",
|
|
"Get-PnpDevice -Class AudioEndpoint | Format-List FriendlyName,Status,InstanceId"},
|
|
2500) << "\n";
|
|
#endif
|
|
|
|
ts << "Hints\n";
|
|
ts << " - If your system shows a device but QMediaDevices does not, suspect QtMultimedia backend/plugin mismatch.\n";
|
|
#ifdef Q_OS_LINUX
|
|
ts << " - Try launching with QT_AUDIO_BACKEND=pulseaudio.\n";
|
|
#endif
|
|
ts << " - If QT_DEBUG_PLUGINS=1 is set before launch, plugin load failures will print to stderr.\n";
|
|
|
|
return s;
|
|
}
|
|
|
|
QString Configuration::impl::run_cmd(QString const& program,
|
|
QStringList const& args,
|
|
int timeout_ms = 1200)
|
|
{
|
|
QProcess p;
|
|
p.setProgram(program);
|
|
p.setArguments(args);
|
|
p.setProcessChannelMode(QProcess::MergedChannels);
|
|
p.start();
|
|
|
|
if (!p.waitForStarted(200))
|
|
return QString("[failed to start: %1]\n").arg(program);
|
|
|
|
if (!p.waitForFinished(timeout_ms)) {
|
|
p.kill();
|
|
p.waitForFinished(200);
|
|
return QString("[timeout after %1ms: %2]\n").arg(timeout_ms).arg(program);
|
|
}
|
|
|
|
const auto out = QString::fromLocal8Bit(p.readAll());
|
|
return out.isEmpty() ? QString("[no output: %1]\n").arg(program) : out;
|
|
}
|
|
|
|
void Configuration::impl::append_env(QTextStream& ts, char const* key)
|
|
{
|
|
const auto v = qEnvironmentVariable(key);
|
|
ts << " " << key << "=" << (v.isEmpty() ? "<unset>" : v) << "\n";
|
|
}
|
|
|
|
QString Configuration::impl::dump_devices(QString const& title,
|
|
QList<QAudioDevice> const& devices)
|
|
{
|
|
QString s;
|
|
QTextStream ts(&s);
|
|
|
|
ts << title << " (" << devices.size() << ")\n";
|
|
for (auto const& d : devices) {
|
|
ts << " - description: " << d.description() << "\n";
|
|
ts << " id: " << d.id() << "\n";
|
|
ts << " mode: " << int(d.mode()) << "\n";
|
|
ts << " chConfig: " << int(d.channelConfiguration()) << "\n";
|
|
ts << " prefFmt: "
|
|
<< d.preferredFormat().sampleRate() << " Hz, "
|
|
<< d.preferredFormat().channelCount() << " ch, "
|
|
<< int(d.preferredFormat().sampleFormat()) << "\n";
|
|
ts << " formats: ";
|
|
for (auto const f : d.supportedSampleFormats())
|
|
ts << int(f) << " ";
|
|
ts << "\n";
|
|
}
|
|
ts << "\n";
|
|
return s;
|
|
}
|
|
|
|
QString Configuration::test_notification_path(const QString &key) const {
|
|
if (!m_->ui_->notifications_check_box->isChecked()) {
|
|
return "";
|
|
}
|
|
// Walk the live table to find the path label for this key
|
|
for (int i = 0; i < m_->ui_->notifications_table_widget->rowCount(); i++) {
|
|
auto item = m_->ui_->notifications_table_widget->item(i, 0);
|
|
if (!item || item->data(Qt::UserRole).toString() != key) continue;
|
|
auto label = qobject_cast<QLabel *>(
|
|
m_->ui_->notifications_table_widget->cellWidget(i, 2));
|
|
if (label) return label->text();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
#if !defined(QT_NO_DEBUG_STREAM)
|
|
ENUM_QDEBUG_OPS_IMPL(Configuration, DataMode);
|
|
#endif
|
|
|
|
ENUM_QDATASTREAM_OPS_IMPL(Configuration, DataMode);
|
|
|
|
ENUM_CONVERSION_OPS_IMPL(Configuration, DataMode);
|
|
|
|
Q_LOGGING_CATEGORY(configuration_js8, "configuration.js8", QtWarningMsg)
|