#include "Configuration.hpp" // // 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.hpp). 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.hpp). // // 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.hpp) // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pimpl_impl.hpp" #include "Logger.hpp" #include "qt_helpers.hpp" #include "MetaDataRegistry.hpp" #include "SettingsGroup.hpp" #include "widgets/FrequencyLineEdit.hpp" #include "widgets/FrequencyDeltaLineEdit.hpp" #include "item_delegates/CandidateKeyFilter.hpp" #include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/FrequencyDelegate.hpp" #include "item_delegates/FrequencyDeltaDelegate.hpp" #include "Transceiver/TransceiverFactory.hpp" #include "Transceiver/Transceiver.hpp" #include "models/Bands.hpp" #include "models/IARURegions.hpp" #include "models/Modes.hpp" #include "models/FrequencyList.hpp" #include "models/StationList.hpp" #include "Network/NetworkServerLookup.hpp" #include "widgets/MessageBox.hpp" #include "validators/MaidenheadLocatorValidator.hpp" #include "validators/CallsignValidator.hpp" #include "Network/LotWUsers.hpp" #include "models/DecodeHighlightingModel.hpp" #include "logbook/logbook.h" #include "widgets/LazyFillComboBox.hpp" #include "ui_Configuration.h" #include "moc_Configuration.cpp" namespace { // these undocumented flag values when stored in (Qt::UserRole - 1) // of a ComboBox item model index allow the item to be enabled or // disabled int const combo_box_item_enabled (32 | 1); int const combo_box_item_disabled (0); // QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"}; QRegularExpression message_alphabet {"[- @A-Za-z0-9+./?#<>]*"}; QRegularExpression RTTY_roundup_exchange_re { R"( ( AL|AZ|AR|CA|CO|CT|DE|FL|GA # 48 contiguous states |ID|IL|IN|IA|KS|KY|LA|ME|MD |MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ |NM|NY|NC|ND|OH|OK|OR|PA|RI|SC |SD|TN|TX|UT|VT|VA|WA|WV|WI|WY |NB|NS|QC|ON|MB|SK|AB|BC|NWT|NF # VE provinces |LB|NU|YT|PEI |DC # District of Columbia |DX # anyone else |SCC # Slovenia Contest Club contest ) )", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption}; QRegularExpression field_day_exchange_re { R"( ( [1-9] # # transmitters (1 to 32 inc.) |[0-2]\d |3[0-2] ) [A-F]\ * # class and optional space ( AB|AK|AL|AR|AZ|BC|CO|CT|DE|EB # ARRL/RAC section |EMA|ENY|EPA|EWA|GA|GTA|IA|ID |IL|IN|KS|KY|LA|LAX|MAR|MB|MDC |ME|MI|MN|MO|MS|MT|NC|ND|NE|NFL |NH|NL|NLI|NM|NNJ|NNY|NT|NTX|NV |OH|OK|ONE|ONN|ONS|OR|ORG|PAC|PE |PR|QC|RI|SB|SC|SCV|SD|SDG|SF |SFL|SJV|SK|SNJ|STX|SV|TN|UT|VA |VI|VT|WCF|WI|WMA|WNY|WPA|WTX |WV|WWA|WY |DX # anyone else ) )", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption}; // Magic numbers for file validation constexpr quint32 qrg_magic {0xadbccbdb}; constexpr quint32 qrg_version {100}; // M.mm } // // Dialog to get a new Frequency item // class FrequencyDialog final : public QDialog { Q_OBJECT public: using Item = FrequencyList_v2::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_); 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 ())}; } private: QComboBox region_combo_box_; QComboBox mode_combo_box_; FrequencyLineEdit frequency_line_edit_; }; // // Dialog to get a new Station item // class StationDialog final : public QDialog { Q_OBJECT public: explicit StationDialog (StationList const * stations, Bands * bands, QWidget * parent = nullptr) : QDialog {parent} , filtered_bands_ {new CandidateKeyFilter {bands, stations, 0, 0}} { setWindowTitle (QApplication::applicationName () + " - " + tr ("Add Station")); band_.setModel (filtered_bands_.data ()); auto form_layout = new QFormLayout (); form_layout->addRow (tr ("&Band:"), &band_); form_layout->addRow (tr ("&Offset (MHz):"), &delta_); form_layout->addRow (tr ("&Antenna:"), &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); if (delta_.text ().isEmpty ()) { delta_.setText ("0"); } } StationList::Station station () const { return {band_.currentText (), delta_.frequency_delta (), description_.text ()}; } int exec () override { filtered_bands_->set_active_key (); return QDialog::exec (); } private: QScopedPointer filtered_bands_; QComboBox band_; FrequencyDeltaLineEdit delta_; 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: explicit MessageItemDelegate (QObject * parent = nullptr) : QStyledItemDelegate {parent} { } 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; using audio_info_type = QPair >; explicit impl (Configuration * self , QNetworkAccessManager * network_manager , QDir const& temp_directory , QSettings * settings , LogBook * logbook , 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 AudioDevices; void read_settings (); void write_settings (); void find_audio_devices (); QAudioDeviceInfo find_audio_device (QAudio::Mode, QComboBox *, QString const& device_name); void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *); void update_audio_channels (QComboBox const *, int, QComboBox *, bool); void load_network_interfaces (CheckableItemComboBox *, QStringList current); Q_SLOT void validate_network_interfaces (QString const&); QStringList get_selected_network_interfaces (CheckableItemComboBox *); Q_SLOT void host_info_results (QHostInfo); void check_multicast (QHostAddress const&); 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 (); 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 delete_frequencies (); void load_frequencies (); void merge_frequencies (); void save_frequencies (); void reset_frequencies (); void insert_frequency (); FrequencyList_v2::FrequencyItems read_frequencies_file (QString const&); void delete_stations (); void insert_station (); Q_SLOT void on_font_push_button_clicked (); Q_SLOT void on_decoded_text_font_push_button_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 (int); Q_SLOT void on_CAT_stop_bits_button_group_buttonClicked (int); Q_SLOT void on_CAT_handshake_button_group_buttonClicked (int); Q_SLOT void on_CAT_poll_interval_spin_box_valueChanged (int); Q_SLOT void on_split_mode_button_group_buttonClicked (int); 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 (int); Q_SLOT void on_add_macro_line_edit_editingFinished (); Q_SLOT void delete_macro (); void delete_selected_macros (QModelIndexList); Q_SLOT void on_udp_server_line_edit_textChanged (QString const&); Q_SLOT void on_udp_server_line_edit_editingFinished (); Q_SLOT void on_save_path_select_push_button_clicked (bool); Q_SLOT void on_azel_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_reset_highlighting_to_defaults_push_button_clicked (bool); Q_SLOT void on_rescan_log_push_button_clicked (bool); Q_SLOT void on_LotW_CSV_fetch_push_button_clicked (bool); Q_SLOT void on_cbx2ToneSpacing_clicked(bool); Q_SLOT void on_cbx4ToneSpacing_clicked(bool); Q_SLOT void on_prompt_to_log_check_box_clicked(bool); Q_SLOT void on_cbAutoLog_clicked(bool); Q_SLOT void on_Field_Day_Exchange_textEdited (QString const&); Q_SLOT void on_RTTY_Exchange_textEdited (QString const&); // 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 rig_connections_; QScopedPointer ui_; QNetworkAccessManager * network_manager_; QSettings * settings_; LogBook * logbook_; QDir doc_dir_; QDir data_dir_; QDir temp_dir_; QDir writeable_data_dir_; QDir default_save_directory_; QDir save_directory_; QDir default_azel_directory_; QDir azel_directory_; QFont font_; QFont next_font_; QFont decoded_text_font_; QFont next_decoded_text_font_; LotWUsers lotw_users_; bool restart_sound_input_device_; bool restart_sound_output_device_; Type2MsgGen type_2_msg_gen_; QStringListModel macros_; RearrangableMacrosModel next_macros_; QAction * macro_delete_action_; Bands bands_; IARURegions regions_; IARURegions::Region region_; Modes modes_; FrequencyList_v2 frequencies_; FrequencyList_v2 next_frequencies_; StationList stations_; StationList next_stations_; FrequencyDelta current_offset_; FrequencyDelta current_tx_offset_; 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_delete_action_; QAction station_insert_action_; StationDialog * station_dialog_; DecodeHighlightingModel decode_highlighing_model_; DecodeHighlightingModel next_decode_highlighing_model_; bool highlight_by_mode_; bool highlight_only_fields_; bool include_WAE_entities_; int LotW_days_since_upload_; 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_; // configuration fields that we publish QString my_callsign_; QString my_grid_; QString FD_exchange_; QString RTTY_exchange_; qint32 id_interval_; qint32 ntrials_; qint32 aggressive_; qint32 RxBandwidth_; double degrade_; double txDelay_; bool id_after_73_; bool tx_QSY_allowed_; bool spot_to_psk_reporter_; bool psk_reporter_tcpip_; bool monitor_off_at_startup_; bool monitor_last_used_; bool log_as_RTTY_; bool report_in_comments_; bool prompt_to_log_; bool autoLog_; bool decodes_from_top_; bool insert_blank_; bool DXCC_; bool ppfx_; bool clear_DX_; bool miles_; bool quick_call_; bool disable_TX_on_73_; bool force_call_1st_; bool alternate_bindings_; int watchdog_; bool TX_messages_; bool enable_VHF_features_; bool decode_at_52s_; bool single_decode_; bool twoPass_; bool bSpecialOp_; int SelectedActivity_; bool x2ToneSpacing_; bool x4ToneSpacing_; bool use_dynamic_grid_; QString opCall_; QString udp_server_name_; bool udp_server_name_edited_; int dns_lookup_id_; port_type udp_server_port_; QStringList udp_interface_names_; QString loopback_interface_name_; int udp_TTL_; QString n1mm_server_name_; port_type n1mm_server_port_; bool broadcast_to_n1mm_; bool accept_udp_requests_; bool udpWindowToFront_; bool udpWindowRestore_; DataMode data_mode_; bool bLowSidelobes_; bool pwrBandTxMemory_; bool pwrBandTuneMemory_; QAudioDeviceInfo audio_input_device_; QAudioDeviceInfo next_audio_input_device_; AudioDevice::Channel audio_input_channel_; AudioDevice::Channel next_audio_input_channel_; QAudioDeviceInfo audio_output_device_; QAudioDeviceInfo next_audio_output_device_; AudioDevice::Channel audio_output_channel_; AudioDevice::Channel next_audio_output_channel_; friend class Configuration; }; #include "Configuration.moc" // delegate to implementation class Configuration::Configuration (QNetworkAccessManager * network_manager, QDir const& temp_directory, QSettings * settings, LogBook * logbook, QWidget * parent) : m_ {this, network_manager, temp_directory, settings, logbook, parent} { } Configuration::~Configuration () { } QDir Configuration::doc_dir () const {return m_->doc_dir_;} QDir Configuration::data_dir () const {return m_->data_dir_;} 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 ();} QAudioDeviceInfo const& Configuration::audio_input_device () const {return m_->audio_input_device_;} AudioDevice::Channel Configuration::audio_input_channel () const {return m_->audio_input_channel_;} QAudioDeviceInfo const& Configuration::audio_output_device () const {return m_->audio_output_device_;} AudioDevice::Channel Configuration::audio_output_channel () const {return m_->audio_output_channel_;} bool Configuration::restart_audio_input () const {return m_->restart_sound_input_device_;} bool Configuration::restart_audio_output () const {return m_->restart_sound_output_device_;} auto Configuration::type_2_msg_gen () const -> Type2MsgGen {return m_->type_2_msg_gen_;} QString Configuration::my_callsign () const {return m_->my_callsign_;} QFont Configuration::text_font () const {return m_->font_;} QFont Configuration::decoded_text_font () const {return m_->decoded_text_font_;} qint32 Configuration::id_interval () const {return m_->id_interval_;} qint32 Configuration::ntrials() const {return m_->ntrials_;} qint32 Configuration::aggressive() const {return m_->aggressive_;} double Configuration::degrade() const {return m_->degrade_;} double Configuration::txDelay() const {return m_->txDelay_;} qint32 Configuration::RxBandwidth() const {return m_->RxBandwidth_;} bool Configuration::id_after_73 () const {return m_->id_after_73_;} bool Configuration::tx_QSY_allowed () const {return m_->tx_QSY_allowed_;} bool Configuration::spot_to_psk_reporter () const { // rig must be open and working to spot externally return is_transceiver_online () && m_->spot_to_psk_reporter_; } bool Configuration::psk_reporter_tcpip () const {return m_->psk_reporter_tcpip_;} bool Configuration::monitor_off_at_startup () const {return m_->monitor_off_at_startup_;} bool Configuration::monitor_last_used () const {return m_->rig_is_dummy_ || m_->monitor_last_used_;} bool Configuration::log_as_RTTY () const {return m_->log_as_RTTY_;} bool Configuration::report_in_comments () const {return m_->report_in_comments_;} bool Configuration::prompt_to_log () const {return m_->prompt_to_log_;} bool Configuration::autoLog() const {return m_->autoLog_;} bool Configuration::decodes_from_top () const {return m_->decodes_from_top_;} 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::clear_DX () const {return m_->clear_DX_;} bool Configuration::miles () const {return m_->miles_;} bool Configuration::quick_call () const {return m_->quick_call_;} bool Configuration::disable_TX_on_73 () const {return m_->disable_TX_on_73_;} bool Configuration::force_call_1st() const {return m_->force_call_1st_;} bool Configuration::alternate_bindings() const {return m_->alternate_bindings_;} int Configuration::watchdog () const {return m_->watchdog_;} bool Configuration::TX_messages () const {return m_->TX_messages_;} bool Configuration::enable_VHF_features () const {return m_->enable_VHF_features_;} bool Configuration::decode_at_52s () const {return m_->decode_at_52s_;} bool Configuration::single_decode () const {return m_->single_decode_;} bool Configuration::twoPass() const {return m_->twoPass_;} bool Configuration::x2ToneSpacing() const {return m_->x2ToneSpacing_;} bool Configuration::x4ToneSpacing() const {return m_->x4ToneSpacing_;} bool Configuration::split_mode () const {return m_->split_mode ();} QString Configuration::opCall() const {return m_->opCall_;} void Configuration::opCall (QString const& call) {m_->opCall_ = call;} QString Configuration::udp_server_name () const {return m_->udp_server_name_;} auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;} QStringList Configuration::udp_interface_names () const {return m_->udp_interface_names_;} int Configuration::udp_TTL () const {return m_->udp_TTL_;} bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;} 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::lowSidelobes() const {return m_->bLowSidelobes_;} bool Configuration::udpWindowToFront () const {return m_->udpWindowToFront_;} bool Configuration::udpWindowRestore () const {return m_->udpWindowRestore_;} 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_;} IARURegions::Region Configuration::region () const {return m_->region_;} FrequencyList_v2 * Configuration::frequencies () {return &m_->frequencies_;} FrequencyList_v2 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_;} QDir Configuration::azel_directory () const {return m_->azel_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_;} LotWUsers const& Configuration::lotw_users () const {return m_->lotw_users_;} DecodeHighlightingModel const& Configuration::decode_highlighting () const {return m_->decode_highlighing_model_;} bool Configuration::highlight_by_mode () const {return m_->highlight_by_mode_;} bool Configuration::highlight_only_fields () const {return m_->highlight_only_fields_;} bool Configuration::include_WAE_entities () const {return m_->include_WAE_entities_;} 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_->current_offset_; 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 () { LOG_TRACE (m_->cached_rig_state_); return m_->have_rig (); } int Configuration::transceiver_resolution () const { return m_->rig_resolution_; } void Configuration::transceiver_offline () { LOG_TRACE (m_->cached_rig_state_); m_->close_rig (); } void Configuration::transceiver_frequency (Frequency f) { LOG_TRACE (f << ' ' << m_->cached_rig_state_); m_->transceiver_frequency (f); } void Configuration::transceiver_tx_frequency (Frequency f) { LOG_TRACE (f << ' ' << m_->cached_rig_state_); m_->transceiver_tx_frequency (f); } void Configuration::transceiver_mode (MODE mode) { LOG_TRACE (mode << ' ' << m_->cached_rig_state_); m_->transceiver_mode (mode); } void Configuration::transceiver_ptt (bool on) { LOG_TRACE (on << ' ' << m_->cached_rig_state_); m_->transceiver_ptt (on); } void Configuration::sync_transceiver (bool force_signal, bool enforce_mode_and_split) { LOG_TRACE ("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_ = QAudioDeviceInfo {}; } void Configuration::invalidate_audio_output_device (QString /* error */) { m_->audio_output_device_ = QAudioDeviceInfo {}; } 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 the_grid = m_->my_grid_; if (m_->use_dynamic_grid_ && m_->dynamic_grid_.size () >= 4) { the_grid = m_->dynamic_grid_; } return the_grid; } QString Configuration::Field_Day_Exchange() const { return m_->FD_exchange_; } void Configuration::setEU_VHF_Contest() { m_->bSpecialOp_=true; m_->ui_->gbSpecialOpActivity->setChecked(m_->bSpecialOp_); m_->ui_->rbEU_VHF_Contest->setChecked(true); m_->SelectedActivity_ = static_cast (SpecialOperatingActivity::EU_VHF); m_->write_settings(); } QString Configuration::RTTY_Exchange() const { return m_->RTTY_exchange_; } auto Configuration::special_op_id () const -> SpecialOperatingActivity { return m_->bSpecialOp_ ? static_cast (m_->SelectedActivity_) : SpecialOperatingActivity::NONE; } void Configuration::set_location (QString const& grid_descriptor) { // change the dynamic grid // qDebug () << "Configuration::set_location - location:" << grid_descriptor; m_->dynamic_grid_ = grid_descriptor.trimmed (); } namespace { #if defined (Q_OS_MAC) char const * app_root = "/../../../"; #else char const * app_root = "/../"; #endif QString doc_path () { #if CMAKE_BUILD if (QDir::isRelativePath (CMAKE_INSTALL_DOCDIR)) { return QApplication::applicationDirPath () + app_root + CMAKE_INSTALL_DOCDIR; } return CMAKE_INSTALL_DOCDIR; #else return QApplication::applicationDirPath (); #endif } QString data_path () { #if CMAKE_BUILD if (QDir::isRelativePath (CMAKE_INSTALL_DATADIR)) { return QApplication::applicationDirPath () + app_root + CMAKE_INSTALL_DATADIR + QChar {'/'} + CMAKE_PROJECT_NAME; } return CMAKE_INSTALL_DATADIR; #else return QApplication::applicationDirPath (); #endif } } Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network_manager , QDir const& temp_directory, QSettings * settings, LogBook * logbook , QWidget * parent) : QDialog {parent} , self_ {self} , transceiver_thread_ {nullptr} , ui_ {new Ui::configuration_dialog} , network_manager_ {network_manager} , settings_ {settings} , logbook_ {logbook} , doc_dir_ {doc_path ()} , data_dir_ {data_path ()} , temp_dir_ {temp_directory} , writeable_data_dir_ {QStandardPaths::writableLocation (QStandardPaths::DataLocation)} , lotw_users_ {network_manager_} , restart_sound_input_device_ {false} , restart_sound_output_device_ {false} , frequencies_ {&bands_} , next_frequencies_ {&bands_} , stations_ {&bands_} , next_stations_ {&bands_} , current_offset_ {0} , current_tx_offset_ {0} , frequency_dialog_ {new FrequencyDialog {®ions_, &modes_, this}} , station_delete_action_ {tr ("&Delete"), nullptr} , station_insert_action_ {tr ("&Insert ..."), nullptr} , station_dialog_ {new StationDialog {&next_stations_, &bands_, this}} , highlight_by_mode_ {false} , highlight_only_fields_ {false} , include_WAE_entities_ {false} , LotW_days_since_upload_ {0} , 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} , degrade_ {0.} // initialize to zero each run, not // saved in settings , udp_server_name_edited_ {false} , dns_lookup_id_ {-1} { ui_->setupUi (this); { // Make sure the default save directory exists QString save_dir {"save"}; default_save_directory_ = writeable_data_dir_; default_azel_directory_ = writeable_data_dir_; if (!default_save_directory_.mkpath (save_dir) || !default_save_directory_.cd (save_dir)) { MessageBox::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)) { MessageBox::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"}; } // 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}); load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_); update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, 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 (QAudio::AudioOutput, ui_->sound_output_combo_box, &next_audio_output_device_); update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true); ui_->sound_output_channel_combo_box->setCurrentIndex (next_audio_output_channel_); QGuiApplication::restoreOverrideCursor (); }); // set up dynamic loading of network interfaces connect (ui_->udp_interfaces_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor}); load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_); QGuiApplication::restoreOverrideCursor (); }); connect (ui_->udp_interfaces_combo_box, &QComboBox::currentTextChanged, this, &Configuration::impl::validate_network_interfaces); // set up LoTW users CSV file fetching connect (&lotw_users_, &LotWUsers::load_finished, [this] () { ui_->LotW_CSV_fetch_push_button->setEnabled (true); }); lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv")); // // validation // ui_->callsign_line_edit->setValidator (new CallsignValidator {this}); ui_->grid_line_edit->setValidator (new MaidenheadLocatorValidator {this}); ui_->add_macro_line_edit->setValidator (new QRegularExpressionValidator {message_alphabet, this}); ui_->Field_Day_Exchange->setValidator (new QRegularExpressionValidator {field_day_exchange_re, this}); ui_->RTTY_Exchange->setValidator (new QRegularExpressionValidator {RTTY_roundup_exchange_re, this}); // // 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); ui_->special_op_activity_button_group->setId (ui_->rbNA_VHF_Contest, static_cast (SpecialOperatingActivity::NA_VHF)); ui_->special_op_activity_button_group->setId (ui_->rbEU_VHF_Contest, static_cast (SpecialOperatingActivity::EU_VHF)); ui_->special_op_activity_button_group->setId (ui_->rbField_Day, static_cast (SpecialOperatingActivity::FIELD_DAY)); ui_->special_op_activity_button_group->setId (ui_->rbRTTY_Roundup, static_cast (SpecialOperatingActivity::RTTY)); ui_->special_op_activity_button_group->setId (ui_->rbWW_DIGI, static_cast (SpecialOperatingActivity::WW_DIGI)); ui_->special_op_activity_button_group->setId (ui_->rbFox, static_cast (SpecialOperatingActivity::FOX)); ui_->special_op_activity_button_group->setId (ui_->rbHound, static_cast (SpecialOperatingActivity::HOUND)); // // setup PTT port combo box drop down content // fill_port_combo_box (ui_->PTT_port_combo_box); ui_->PTT_port_combo_box->addItem ("CAT"); ui_->PTT_port_combo_box->setItemData (ui_->PTT_port_combo_box->count () - 1, "Delegate to proxy CAT service", Qt::ToolTipRole); // // setup hooks to keep audio channels aligned with devices // { using namespace std; using namespace std::placeholders; function cb (bind (&Configuration::impl::update_audio_channels, this, ui_->sound_input_combo_box, _1, ui_->sound_input_channel_combo_box, false)); connect (ui_->sound_input_combo_box, static_cast (&QComboBox::currentIndexChanged), cb); cb = bind (&Configuration::impl::update_audio_channels, this, ui_->sound_output_combo_box, _1, ui_->sound_output_channel_combo_box, true); connect (ui_->sound_output_combo_box, static_cast (&QComboBox::currentIndexChanged), cb); } // // 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_v2::frequency_column); ui_->frequencies_table_view->setModel (&next_frequencies_); ui_->frequencies_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->frequencies_table_view->horizontalHeader ()->setResizeContentsPrecision (0); ui_->frequencies_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->frequencies_table_view->verticalHeader ()->setResizeContentsPrecision (0); ui_->frequencies_table_view->sortByColumn (FrequencyList_v2::frequency_column, Qt::AscendingOrder); ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true); // delegates ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::frequency_column, new FrequencyDelegate {this}); ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::region_column, new ForeignKeyDelegate {®ions_, 0, this}); ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::mode_column, new ForeignKeyDelegate {&modes_, 0, this}); // actions frequency_delete_action_ = new QAction {tr ("&Delete"), ui_->frequencies_table_view}; 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::band_column); ui_->stations_table_view->setModel (&next_stations_); ui_->stations_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->stations_table_view->horizontalHeader ()->setResizeContentsPrecision (0); ui_->stations_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->stations_table_view->verticalHeader ()->setResizeContentsPrecision (0); ui_->stations_table_view->sortByColumn (StationList::band_column, Qt::AscendingOrder); // stations delegates ui_->stations_table_view->setItemDelegateForColumn (StationList::offset_column, new FrequencyDeltaDelegate {this}); ui_->stations_table_view->setItemDelegateForColumn (StationList::band_column, new ForeignKeyDelegate {&bands_, &next_stations_, 0, StationList::band_column, this}); // stations actions ui_->stations_table_view->addAction (&station_delete_action_); connect (&station_delete_action_, &QAction::triggered, this, &Configuration::impl::delete_stations); ui_->stations_table_view->addAction (&station_insert_action_); connect (&station_insert_action_, &QAction::triggered, this, &Configuration::impl::insert_station); // // colours and highlighting setup // ui_->highlighting_list_view->setModel (&next_decode_highlighing_model_); 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_; bool fetch_if_needed {false}; for (auto const& item : decode_highlighing_model_.items ()) { if (DecodeHighlightingModel::Highlight::LotW == item.type_) { fetch_if_needed = item.enabled_; break; } } // load the LoTW users dictionary if it exists, fetch and load if it // doesn't and we need it lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), fetch_if_needed); 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_; restart_sound_input_device_ = false; restart_sound_output_device_ = false; { SettingsGroup g {settings_, "Configuration"}; find_audio_devices (); } auto pal = ui_->callsign_line_edit->palette (); if (my_callsign_.isEmpty ()) { pal.setColor (QPalette::Base, "#ffccff"); } else { pal.setColor (QPalette::Base, Qt::white); } ui_->callsign_line_edit->setPalette (pal); ui_->grid_line_edit->setPalette (pal); ui_->callsign_line_edit->setText (my_callsign_); ui_->grid_line_edit->setText (my_grid_); ui_->use_dynamic_grid->setChecked(use_dynamic_grid_); ui_->CW_id_interval_spin_box->setValue (id_interval_); ui_->sbNtrials->setValue (ntrials_); ui_->sbTxDelay->setValue (txDelay_); ui_->sbAggressive->setValue (aggressive_); ui_->sbDegrade->setValue (degrade_); ui_->sbBandwidth->setValue (RxBandwidth_); ui_->PTT_method_button_group->button (rig_params_.ptt_type)->setChecked (true); ui_->save_path_display_label->setText (save_directory_.absolutePath ()); ui_->azel_path_display_label->setText (azel_directory_.absolutePath ()); ui_->CW_id_after_73_check_box->setChecked (id_after_73_); ui_->tx_QSY_check_box->setChecked (tx_QSY_allowed_); ui_->psk_reporter_check_box->setChecked (spot_to_psk_reporter_); ui_->psk_reporter_tcpip_check_box->setChecked (psk_reporter_tcpip_); ui_->monitor_off_check_box->setChecked (monitor_off_at_startup_); ui_->monitor_last_used_check_box->setChecked (monitor_last_used_); ui_->log_as_RTTY_check_box->setChecked (log_as_RTTY_); ui_->report_in_comments_check_box->setChecked (report_in_comments_); ui_->prompt_to_log_check_box->setChecked (prompt_to_log_); ui_->cbAutoLog->setChecked(autoLog_); ui_->decodes_from_top_check_box->setChecked (decodes_from_top_); ui_->insert_blank_check_box->setChecked (insert_blank_); ui_->DXCC_check_box->setChecked (DXCC_); ui_->ppfx_check_box->setChecked (ppfx_); ui_->clear_DX_check_box->setChecked (clear_DX_); ui_->miles_check_box->setChecked (miles_); ui_->quick_call_check_box->setChecked (quick_call_); ui_->disable_TX_on_73_check_box->setChecked (disable_TX_on_73_); ui_->force_call_1st_check_box->setChecked (force_call_1st_); ui_->alternate_bindings_check_box->setChecked (alternate_bindings_); ui_->tx_watchdog_spin_box->setValue (watchdog_); ui_->TX_messages_check_box->setChecked (TX_messages_); ui_->enable_VHF_features_check_box->setChecked(enable_VHF_features_); ui_->decode_at_52s_check_box->setChecked(decode_at_52s_); ui_->single_decode_check_box->setChecked(single_decode_); ui_->cbTwoPass->setChecked(twoPass_); ui_->gbSpecialOpActivity->setChecked(bSpecialOp_); ui_->special_op_activity_button_group->button (SelectedActivity_)->setChecked (true); ui_->cbx2ToneSpacing->setChecked(x2ToneSpacing_); ui_->cbx4ToneSpacing->setChecked(x4ToneSpacing_); ui_->type_2_msg_gen_combo_box->setCurrentIndex (type_2_msg_gen_); 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_->udp_server_line_edit->setText (udp_server_name_); on_udp_server_line_edit_editingFinished (); ui_->udp_server_port_spin_box->setValue (udp_server_port_); load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_); if (!udp_interface_names_.size ()) { udp_interface_names_ = get_selected_network_interfaces (ui_->udp_interfaces_combo_box); } ui_->udp_TTL_spin_box->setValue (udp_TTL_); ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_); 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_->udpWindowToFront->setChecked(udpWindowToFront_); ui_->udpWindowRestore->setChecked(udpWindowRestore_); ui_->calibration_intercept_spin_box->setValue (calibration_.intercept); ui_->calibration_slope_ppm_spin_box->setValue (calibration_.slope_ppm); ui_->rbLowSidelobes->setChecked(bLowSidelobes_); if(!bLowSidelobes_) ui_->rbMaxSensitivity->setChecked(true); 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 ()); next_decode_highlighing_model_.items (decode_highlighing_model_.items ()); ui_->highlight_by_mode_check_box->setChecked (highlight_by_mode_); ui_->only_fields_check_box->setChecked (highlight_only_fields_); ui_->include_WAE_check_box->setChecked (include_WAE_entities_); ui_->LotW_days_since_upload_spin_box->setValue (LotW_days_since_upload_); 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 ("window/geometry", saveGeometry ()); QDialog::done (r); } void Configuration::impl::read_settings () { SettingsGroup g {settings_, "Configuration"}; restoreGeometry (settings_->value ("window/geometry").toByteArray ()); my_callsign_ = settings_->value ("MyCall", QString {}).toString (); my_grid_ = settings_->value ("MyGrid", QString {}).toString (); FD_exchange_ = settings_->value ("Field_Day_Exchange",QString {}).toString (); RTTY_exchange_ = settings_->value ("RTTY_Exchange",QString {}).toString (); ui_->Field_Day_Exchange->setText(FD_exchange_); ui_->RTTY_Exchange->setText(RTTY_exchange_); if (next_font_.fromString (settings_->value ("Font", QGuiApplication::font ().toString ()).toString ()) && next_font_ != font_) { font_ = next_font_; Q_EMIT self_->text_font_changed (font_); } else { next_font_ = font_; } if (next_decoded_text_font_.fromString (settings_->value ("DecodedTextFont", "Courier, 10").toString ()) && next_decoded_text_font_ != decoded_text_font_) { decoded_text_font_ = next_decoded_text_font_; next_decode_highlighing_model_.set_font (decoded_text_font_); ui_->highlighting_list_view->reset (); Q_EMIT self_->decoded_text_font_changed (decoded_text_font_); } else { next_decoded_text_font_ = decoded_text_font_; } id_interval_ = settings_->value ("IDint", 0).toInt (); ntrials_ = settings_->value ("nTrials", 6).toInt (); txDelay_ = settings_->value ("TxDelay",0.2).toDouble(); aggressive_ = settings_->value ("Aggressive", 0).toInt (); RxBandwidth_ = settings_->value ("RxBandwidth", 2500).toInt (); save_directory_.setPath (settings_->value ("SaveDir", default_save_directory_.absolutePath ()).toString ()); azel_directory_.setPath (settings_->value ("AzElDir", default_azel_directory_.absolutePath ()).toString ()); type_2_msg_gen_ = settings_->value ("Type2MsgGen", QVariant::fromValue (Configuration::type_2_msg_3_full)).value (); monitor_off_at_startup_ = settings_->value ("MonitorOFF", false).toBool (); monitor_last_used_ = settings_->value ("MonitorLastUsed", false).toBool (); spot_to_psk_reporter_ = settings_->value ("PSKReporter", false).toBool (); psk_reporter_tcpip_ = settings_->value ("PSKReporterTCPIP", false).toBool (); id_after_73_ = settings_->value ("After73", false).toBool (); tx_QSY_allowed_ = settings_->value ("TxQSYAllowed", false).toBool (); use_dynamic_grid_ = settings_->value ("AutoGrid", false).toBool (); macros_.setStringList (settings_->value ("Macros", QStringList {"TNX 73 GL"}).toStringList ()); region_ = settings_->value ("Region", QVariant::fromValue (IARURegions::ALL)).value (); if (settings_->contains ("FrequenciesForRegionModes")) { auto const& v = settings_->value ("FrequenciesForRegionModes"); if (v.isValid ()) { frequencies_.frequency_list (v.value ()); } else { frequencies_.reset_to_defaults (); } } else { frequencies_.reset_to_defaults (); } stations_.station_list (settings_->value ("stations").value ()); auto highlight_items = settings_->value ("DecodeHighlighting", QVariant::fromValue (DecodeHighlightingModel::default_items ())).value (); if (!highlight_items.size ()) highlight_items = DecodeHighlightingModel::default_items (); decode_highlighing_model_.items (highlight_items); highlight_by_mode_ = settings_->value("HighlightByMode", false).toBool (); highlight_only_fields_ = settings_->value("OnlyFieldsSought", false).toBool (); include_WAE_entities_ = settings_->value("IncludeWAEEntities", false).toBool (); LotW_days_since_upload_ = settings_->value ("LotWDaysSinceLastUpload", 365).toInt (); lotw_users_.set_age_constraint (LotW_days_since_upload_); log_as_RTTY_ = settings_->value ("toRTTY", false).toBool (); report_in_comments_ = settings_->value("dBtoComments", false).toBool (); 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 (); rig_params_.stop_bits = settings_->value ("CATStopBits", QVariant::fromValue (TransceiverFactory::default_stop_bits)).value (); rig_params_.handshake = settings_->value ("CATHandshake", QVariant::fromValue (TransceiverFactory::handshake_default)).value (); 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 (); rig_params_.audio_source = settings_->value ("TXAudioSource", QVariant::fromValue (TransceiverFactory::TX_audio_source_front)).value (); rig_params_.ptt_port = settings_->value ("PTTport").toString (); data_mode_ = settings_->value ("DataMode", QVariant::fromValue (data_mode_none)).value (); bLowSidelobes_ = settings_->value("LowSidelobes",true).toBool(); prompt_to_log_ = settings_->value ("PromptToLog", false).toBool (); autoLog_ = settings_->value ("AutoLog", false).toBool (); decodes_from_top_ = settings_->value ("DecodesFromTop", false).toBool (); insert_blank_ = settings_->value ("InsertBlank", false).toBool (); DXCC_ = settings_->value ("DXCCEntity", false).toBool (); ppfx_ = settings_->value ("PrincipalPrefix", false).toBool (); clear_DX_ = settings_->value ("ClearCallGrid", false).toBool (); miles_ = settings_->value ("Miles", false).toBool (); quick_call_ = settings_->value ("QuickCall", false).toBool (); disable_TX_on_73_ = settings_->value ("73TxDisable", false).toBool (); force_call_1st_ = settings_->value ("ForceCallFirst", false).toBool (); alternate_bindings_ = settings_->value ("AlternateBindings", false).toBool (); watchdog_ = settings_->value ("TxWatchdog", 6).toInt (); TX_messages_ = settings_->value ("Tx2QSO", true).toBool (); enable_VHF_features_ = settings_->value("VHFUHF",false).toBool (); decode_at_52s_ = settings_->value("Decode52",false).toBool (); single_decode_ = settings_->value("SingleDecode",false).toBool (); twoPass_ = settings_->value("TwoPass",true).toBool (); bSpecialOp_ = settings_->value("SpecialOpActivity",false).toBool (); SelectedActivity_ = settings_->value("SelectedActivity",1).toInt (); x2ToneSpacing_ = settings_->value("x2ToneSpacing",false).toBool (); x4ToneSpacing_ = settings_->value("x4ToneSpacing",false).toBool (); rig_params_.poll_interval = settings_->value ("Polling", 0).toInt (); rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value (); opCall_ = settings_->value ("OpCall", "").toString (); udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString (); udp_interface_names_ = settings_->value ("UDPInterface").toStringList (); udp_TTL_ = settings_->value ("UDPTTL", 1).toInt (); udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt (); 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 (); udpWindowToFront_ = settings_->value ("udpWindowToFront",false).toBool (); udpWindowRestore_ = settings_->value ("udpWindowRestore",false).toBool (); 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 (); } void Configuration::impl::find_audio_devices () { // // retrieve audio input device // auto saved_name = settings_->value ("SoundInName").toString (); if (next_audio_input_device_.deviceName () != saved_name || next_audio_input_device_.isNull ()) { next_audio_input_device_ = find_audio_device (QAudio::AudioInput, ui_->sound_input_combo_box, saved_name); next_audio_input_channel_ = AudioDevice::fromString (settings_->value ("AudioInputChannel", "Mono").toString ()); update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false); ui_->sound_input_channel_combo_box->setCurrentIndex (next_audio_input_channel_); } // // retrieve audio output device // saved_name = settings_->value("SoundOutName").toString(); if (next_audio_output_device_.deviceName () != saved_name || next_audio_output_device_.isNull ()) { next_audio_output_device_ = find_audio_device (QAudio::AudioOutput, ui_->sound_output_combo_box, saved_name); next_audio_output_channel_ = AudioDevice::fromString (settings_->value ("AudioOutputChannel", "Mono").toString ()); update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true); ui_->sound_output_channel_combo_box->setCurrentIndex (next_audio_output_channel_); } } void Configuration::impl::write_settings () { SettingsGroup g {settings_, "Configuration"}; settings_->setValue ("MyCall", my_callsign_); settings_->setValue ("MyGrid", my_grid_); settings_->setValue ("Field_Day_Exchange", FD_exchange_); settings_->setValue ("RTTY_Exchange", RTTY_exchange_); settings_->setValue ("Font", font_.toString ()); settings_->setValue ("DecodedTextFont", decoded_text_font_.toString ()); settings_->setValue ("IDint", id_interval_); settings_->setValue ("nTrials", ntrials_); settings_->setValue ("TxDelay", txDelay_); settings_->setValue ("Aggressive", aggressive_); settings_->setValue ("RxBandwidth", RxBandwidth_); settings_->setValue ("PTTMethod", QVariant::fromValue (rig_params_.ptt_type)); settings_->setValue ("PTTport", rig_params_.ptt_port); settings_->setValue ("SaveDir", save_directory_.absolutePath ()); settings_->setValue ("AzElDir", azel_directory_.absolutePath ()); if (!audio_input_device_.isNull ()) { settings_->setValue ("SoundInName", audio_input_device_.deviceName ()); settings_->setValue ("AudioInputChannel", AudioDevice::toString (audio_input_channel_)); } if (!audio_output_device_.isNull ()) { settings_->setValue ("SoundOutName", audio_output_device_.deviceName ()); settings_->setValue ("AudioOutputChannel", AudioDevice::toString (audio_output_channel_)); } settings_->setValue ("Type2MsgGen", QVariant::fromValue (type_2_msg_gen_)); settings_->setValue ("MonitorOFF", monitor_off_at_startup_); settings_->setValue ("MonitorLastUsed", monitor_last_used_); settings_->setValue ("PSKReporter", spot_to_psk_reporter_); settings_->setValue ("PSKReporterTCPIP", psk_reporter_tcpip_); settings_->setValue ("After73", id_after_73_); settings_->setValue ("TxQSYAllowed", tx_QSY_allowed_); settings_->setValue ("Macros", macros_.stringList ()); settings_->setValue ("FrequenciesForRegionModes", QVariant::fromValue (frequencies_.frequency_list ())); settings_->setValue ("stations", QVariant::fromValue (stations_.station_list ())); settings_->setValue ("DecodeHighlighting", QVariant::fromValue (decode_highlighing_model_.items ())); settings_->setValue ("HighlightByMode", highlight_by_mode_); settings_->setValue ("OnlyFieldsSought", highlight_only_fields_); settings_->setValue ("IncludeWAEEntities", include_WAE_entities_); settings_->setValue ("LotWDaysSinceLastUpload", LotW_days_since_upload_); settings_->setValue ("toRTTY", log_as_RTTY_); settings_->setValue ("dBtoComments", report_in_comments_); 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 ("LowSidelobes",bLowSidelobes_); settings_->setValue ("PromptToLog", prompt_to_log_); settings_->setValue ("AutoLog", autoLog_); settings_->setValue ("DecodesFromTop", decodes_from_top_); settings_->setValue ("InsertBlank", insert_blank_); settings_->setValue ("DXCCEntity", DXCC_); settings_->setValue ("PrincipalPrefix", ppfx_); settings_->setValue ("ClearCallGrid", clear_DX_); settings_->setValue ("Miles", miles_); settings_->setValue ("QuickCall", quick_call_); settings_->setValue ("73TxDisable", disable_TX_on_73_); settings_->setValue ("ForceCallFirst", force_call_1st_); settings_->setValue ("AlternateBindings", alternate_bindings_); settings_->setValue ("TxWatchdog", 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 ("VHFUHF", enable_VHF_features_); settings_->setValue ("Decode52", decode_at_52s_); settings_->setValue ("SingleDecode", single_decode_); settings_->setValue ("TwoPass", twoPass_); settings_->setValue ("SelectedActivity", SelectedActivity_); settings_->setValue ("SpecialOpActivity", bSpecialOp_); settings_->setValue ("x2ToneSpacing", x2ToneSpacing_); settings_->setValue ("x4ToneSpacing", x4ToneSpacing_); settings_->setValue ("OpCall", opCall_); settings_->setValue ("UDPServer", udp_server_name_); settings_->setValue ("UDPServerPort", udp_server_port_); settings_->setValue ("UDPInterface", QVariant::fromValue (udp_interface_names_)); settings_->setValue ("UDPTTL", udp_TTL_); 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 ("udpWindowToFront", udpWindowToFront_); settings_->setValue ("udpWindowRestore", udpWindowRestore_); 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_grid_); settings_->sync (); } 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 (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 (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); ui_->PTT_port_label->setEnabled (enable_ptt_port); if (CAT_indirect_serial_PTT) { ui_->PTT_port_combo_box->setItemData (ui_->PTT_port_combo_box->findText ("CAT") , combo_box_item_enabled, Qt::UserRole - 1); } else { ui_->PTT_port_combo_box->setItemData (ui_->PTT_port_combo_box->findText ("CAT") , combo_box_item_disabled, Qt::UserRole - 1); 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_->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_->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); } bool Configuration::impl::validate () { if (ui_->sound_input_combo_box->currentIndex () < 0 && next_audio_input_device_.isNull ()) { find_tab (ui_->sound_input_combo_box); MessageBox::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); MessageBox::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); MessageBox::information_message (this, tr ("Invalid audio output device")); // don't reject as we can work without an audio output } if (!ui_->PTT_method_button_group->checkedButton ()->isEnabled ()) { MessageBox::critical_message (this, tr ("Invalid PTT method")); return false; } auto ptt_method = static_cast (ui_->PTT_method_button_group->checkedId ()); auto ptt_port = ui_->PTT_port_combo_box->currentText (); if ((TransceiverFactory::PTT_method_DTR == ptt_method || TransceiverFactory::PTT_method_RTS == ptt_method) && (ptt_port.isEmpty () || combo_box_item_disabled == ui_->PTT_port_combo_box->itemData (ui_->PTT_port_combo_box->findText (ptt_port), Qt::UserRole - 1))) { MessageBox::critical_message (this, tr ("Invalid PTT port")); return false; } if (ui_->rbField_Day->isEnabled () && ui_->rbField_Day->isChecked () && !ui_->Field_Day_Exchange->hasAcceptableInput ()) { find_tab (ui_->Field_Day_Exchange); MessageBox::critical_message (this, tr ("Invalid Contest Exchange") , tr ("You must input a valid ARRL Field Day exchange")); return false; } if (ui_->rbRTTY_Roundup->isEnabled () && ui_->rbRTTY_Roundup->isChecked () && !ui_->RTTY_Exchange->hasAcceptableInput ()) { find_tab (ui_->RTTY_Exchange); MessageBox::critical_message (this, tr ("Invalid Contest Exchange") , tr ("You must input a valid ARRL RTTY Roundup exchange")); return false; } if (dns_lookup_id_ > -1) { MessageBox::information_message (this, tr ("Pending DNS lookup, please try again later")); 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 () { 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 (ui_->CAT_data_bits_button_group->checkedId ()); result.stop_bits = static_cast (ui_->CAT_stop_bits_button_group->checkedId ()); result.handshake = static_cast (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 (ui_->PTT_method_button_group->checkedId ()); result.ptt_port = ui_->PTT_port_combo_box->currentText (); result.audio_source = static_cast (ui_->TX_audio_source_button_group->checkedId ()); result.split_mode = static_cast (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_->text_font_changed (font_); } if (next_decoded_text_font_ != decoded_text_font_) { decoded_text_font_ = next_decoded_text_font_; next_decode_highlighing_model_.set_font (decoded_text_font_); ui_->highlighting_list_view->reset (); Q_EMIT self_->decoded_text_font_changed (decoded_text_font_); } 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 ().first; if (selected_device != next_audio_input_device_) { next_audio_input_device_ = selected_device; } } { auto const& selected_device = ui_->sound_output_combo_box->currentData ().value ().first; if (selected_device != next_audio_output_device_) { next_audio_output_device_ = selected_device; } } if (next_audio_input_channel_ != static_cast (ui_->sound_input_channel_combo_box->currentIndex ())) { next_audio_input_channel_ = static_cast (ui_->sound_input_channel_combo_box->currentIndex ()); } Q_ASSERT (next_audio_input_channel_ <= AudioDevice::Right); if (next_audio_output_channel_ != static_cast (ui_->sound_output_channel_combo_box->currentIndex ())) { next_audio_output_channel_ = static_cast (ui_->sound_output_channel_combo_box->currentIndex ()); } 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; } // qDebug () << "Configure::accept: audio i/p:" << audio_input_device_.deviceName () // << "chan:" << audio_input_channel_ // << "o/p:" << audio_output_device_.deviceName () // << "chan:" << audio_output_channel_ // << "reset i/p:" << restart_sound_input_device_ // << "reset o/p:" << restart_sound_output_device_; my_callsign_ = ui_->callsign_line_edit->text (); my_grid_ = ui_->grid_line_edit->text (); FD_exchange_= ui_->Field_Day_Exchange->text ().toUpper (); RTTY_exchange_= ui_->RTTY_Exchange->text ().toUpper (); spot_to_psk_reporter_ = ui_->psk_reporter_check_box->isChecked (); psk_reporter_tcpip_ = ui_->psk_reporter_tcpip_check_box->isChecked (); id_interval_ = ui_->CW_id_interval_spin_box->value (); ntrials_ = ui_->sbNtrials->value (); txDelay_ = ui_->sbTxDelay->value (); aggressive_ = ui_->sbAggressive->value (); degrade_ = ui_->sbDegrade->value (); RxBandwidth_ = ui_->sbBandwidth->value (); id_after_73_ = ui_->CW_id_after_73_check_box->isChecked (); tx_QSY_allowed_ = ui_->tx_QSY_check_box->isChecked (); monitor_off_at_startup_ = ui_->monitor_off_check_box->isChecked (); monitor_last_used_ = ui_->monitor_last_used_check_box->isChecked (); type_2_msg_gen_ = static_cast (ui_->type_2_msg_gen_combo_box->currentIndex ()); log_as_RTTY_ = ui_->log_as_RTTY_check_box->isChecked (); report_in_comments_ = ui_->report_in_comments_check_box->isChecked (); prompt_to_log_ = ui_->prompt_to_log_check_box->isChecked (); autoLog_ = ui_->cbAutoLog->isChecked(); decodes_from_top_ = ui_->decodes_from_top_check_box->isChecked (); insert_blank_ = ui_->insert_blank_check_box->isChecked (); DXCC_ = ui_->DXCC_check_box->isChecked (); ppfx_ = ui_->ppfx_check_box->isChecked (); clear_DX_ = ui_->clear_DX_check_box->isChecked (); miles_ = ui_->miles_check_box->isChecked (); quick_call_ = ui_->quick_call_check_box->isChecked (); disable_TX_on_73_ = ui_->disable_TX_on_73_check_box->isChecked (); force_call_1st_ = ui_->force_call_1st_check_box->isChecked (); alternate_bindings_ = ui_->alternate_bindings_check_box->isChecked (); watchdog_ = ui_->tx_watchdog_spin_box->value (); TX_messages_ = ui_->TX_messages_check_box->isChecked (); data_mode_ = static_cast (ui_->TX_mode_button_group->checkedId ()); bLowSidelobes_ = ui_->rbLowSidelobes->isChecked(); save_directory_.setPath (ui_->save_path_display_label->text ()); azel_directory_.setPath (ui_->azel_path_display_label->text ()); enable_VHF_features_ = ui_->enable_VHF_features_check_box->isChecked (); decode_at_52s_ = ui_->decode_at_52s_check_box->isChecked (); single_decode_ = ui_->single_decode_check_box->isChecked (); twoPass_ = ui_->cbTwoPass->isChecked (); bSpecialOp_ = ui_->gbSpecialOpActivity->isChecked (); SelectedActivity_ = ui_->special_op_activity_button_group->checkedId(); x2ToneSpacing_ = ui_->cbx2ToneSpacing->isChecked (); x4ToneSpacing_ = ui_->cbx4ToneSpacing->isChecked (); 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(); auto new_server = ui_->udp_server_line_edit->text ().trimmed (); auto new_interfaces = get_selected_network_interfaces (ui_->udp_interfaces_combo_box); if (new_server != udp_server_name_ || new_interfaces != udp_interface_names_) { udp_server_name_ = new_server; udp_interface_names_ = new_interfaces; Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_names_); } auto new_port = ui_->udp_server_port_spin_box->value (); if (new_port != udp_server_port_) { udp_server_port_ = new_port; Q_EMIT self_->udp_server_port_changed (udp_server_port_); } auto new_TTL = ui_->udp_TTL_spin_box->value (); if (new_TTL != udp_TTL_) { udp_TTL_ = new_TTL; Q_EMIT self_->udp_TTL_changed (udp_TTL_); } if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_) { accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked (); Q_EMIT self_->accept_udp_requests_changed (accept_udp_requests_); } n1mm_server_name_ = ui_->n1mm_server_name_line_edit->text (); n1mm_server_port_ = ui_->n1mm_server_port_spin_box->value (); broadcast_to_n1mm_ = ui_->enable_n1mm_broadcast_check_box->isChecked (); udpWindowToFront_ = ui_->udpWindowToFront->isChecked (); udpWindowRestore_ = ui_->udpWindowRestore->isChecked (); 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_v2::frequency_column); } if (stations_.station_list () != next_stations_.station_list ()) { stations_.station_list (next_stations_.station_list ()); stations_.sort (StationList::band_column); } if (decode_highlighing_model_.items () != next_decode_highlighing_model_.items ()) { decode_highlighing_model_.items (next_decode_highlighing_model_.items ()); Q_EMIT self_->decode_highlighting_changed (decode_highlighing_model_); } highlight_by_mode_ = ui_->highlight_by_mode_check_box->isChecked (); highlight_only_fields_ = ui_->only_fields_check_box->isChecked (); include_WAE_entities_ = ui_->include_WAE_check_box->isChecked (); LotW_days_since_upload_ = ui_->LotW_days_since_upload_spin_box->value (); lotw_users_.set_age_constraint (LotW_days_since_upload_); if (ui_->use_dynamic_grid->isChecked() && !use_dynamic_grid_ ) { // turning on so clear it so only the next location update gets used dynamic_grid_.clear (); } use_dynamic_grid_ = ui_->use_dynamic_grid->isChecked(); write_settings (); // make visible to all } void Configuration::impl::reject () { if (dns_lookup_id_ > -1) { QHostInfo::abortHostLookup (dns_lookup_id_); dns_lookup_id_ = -1; } 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 (); } } // qDebug () << "Configure::reject: audio i/p:" << audio_input_device_.deviceName () // << "chan:" << audio_input_channel_ // << "o/p:" << audio_output_device_.deviceName () // << "chan:" << audio_output_channel_ // << "reset i/p:" << restart_sound_input_device_ // << "reset o/p:" << restart_sound_output_device_; QDialog::reject (); } void Configuration::impl::on_font_push_button_clicked () { next_font_ = QFontDialog::getFont (0, next_font_, this); } void Configuration::impl::on_reset_highlighting_to_defaults_push_button_clicked (bool /*checked*/) { if (MessageBox::Yes == MessageBox::query_message (this , tr ("Reset Decode Highlighting") , tr ("Reset all decode highlighting and priorities to default values"))) { next_decode_highlighing_model_.items (DecodeHighlightingModel::default_items ()); } } void Configuration::impl::on_rescan_log_push_button_clicked (bool /*clicked*/) { if (logbook_) logbook_->rescan (); } void Configuration::impl::on_LotW_CSV_fetch_push_button_clicked (bool /*checked*/) { lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), true, true); ui_->LotW_CSV_fetch_push_button->setEnabled (false); } void Configuration::impl::on_decoded_text_font_push_button_clicked () { next_decoded_text_font_ = QFontDialog::getFont (0, decoded_text_font_ , this , tr ("WSJT-X Decoded Text Font Chooser") , QFontDialog::MonospacedFonts ); } 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 (int /* id */) { 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 (int /* id */) { set_rig_invariants (); } void Configuration::impl::on_CAT_stop_bits_button_group_buttonClicked (int /* id */) { 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 (int /* id */) { 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 (open_rig ()) { 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 (int /* id */) { set_rig_invariants (); } 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 ()); ui_->add_macro_line_edit->clear (); } } void Configuration::impl::on_udp_server_line_edit_textChanged (QString const&) { udp_server_name_edited_ = true; } void Configuration::impl::on_udp_server_line_edit_editingFinished () { if (udp_server_name_edited_) { auto const& server = ui_->udp_server_line_edit->text ().trimmed (); QHostAddress ha {server}; if (server.size () && ha.isNull ()) { // queue a host address lookup // qDebug () << "server host DNS lookup:" << server; #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) dns_lookup_id_ = QHostInfo::lookupHost (server, this, &Configuration::impl::host_info_results); #else dns_lookup_id_ = QHostInfo::lookupHost (server, this, SLOT (host_info_results (QHostInfo))); #endif } else { check_multicast (ha); } } } void Configuration::impl::host_info_results (QHostInfo host_info) { if (host_info.lookupId () != dns_lookup_id_) return; dns_lookup_id_ = -1; if (QHostInfo::NoError != host_info.error ()) { MessageBox::critical_message (this, tr ("UDP server DNS lookup failed"), host_info.errorString ()); } else { auto const& server_addresses = host_info.addresses (); // qDebug () << "message server addresses:" << server_addresses; if (server_addresses.size ()) { check_multicast (server_addresses[0]); } } } void Configuration::impl::check_multicast (QHostAddress const& ha) { auto is_multicast = is_multicast_address (ha); ui_->udp_interfaces_label->setVisible (is_multicast); ui_->udp_interfaces_combo_box->setVisible (is_multicast); ui_->udp_TTL_label->setVisible (is_multicast); ui_->udp_TTL_spin_box->setVisible (is_multicast); if (isVisible ()) { if (is_MAC_ambiguous_multicast_address (ha)) { MessageBox::warning_message (this, tr ("MAC-ambiguous multicast groups addresses not supported")); find_tab (ui_->udp_server_line_edit); ui_->udp_server_line_edit->clear (); } } udp_server_name_edited_ = false; } 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_v2::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 () || MessageBox::Yes == MessageBox::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_v2::FrequencyItems Configuration::impl::read_frequencies_file (QString const& file_name) { QFile frequencies_file {file_name}; frequencies_file.open (QFile::ReadOnly); QDataStream ids {&frequencies_file}; FrequencyList_v2::FrequencyItems list; quint32 magic; ids >> magic; if (qrg_magic != magic) { MessageBox::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) { MessageBox::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 ids >> list; if (ids.status () != QDataStream::Ok || !ids.atEnd ()) { MessageBox::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}; frequencies_file.open (QFile::WriteOnly); QDataStream ods {&frequencies_file}; auto selection_model = ui_->frequencies_table_view->selectionModel (); if (selection_model->hasSelection () && MessageBox::Yes == MessageBox::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 (MessageBox::Yes == MessageBox::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_v2::mode_column); } } 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::offset_column); } void Configuration::impl::insert_station () { if (QDialog::Accepted == station_dialog_->exec ()) { ui_->stations_table_view->setCurrentIndex (next_stations_.add (station_dialog_->station ())); ui_->stations_table_view->resizeColumnToContents (StationList::band_column); ui_->stations_table_view->resizeColumnToContents (StationList::offset_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_azel_path_select_push_button_clicked (bool /* checked */) { QFileDialog fd {this, tr ("AzEl Directory"), ui_->azel_path_display_label->text ()}; fd.setFileMode (QFileDialog::Directory); fd.setOption (QFileDialog::ShowDirsOnly); if (fd.exec ()) { if (fd.selectedFiles ().size ()) { ui_->azel_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 } void Configuration::impl::on_prompt_to_log_check_box_clicked(bool checked) { if(checked) ui_->cbAutoLog->setChecked(false); } void Configuration::impl::on_cbAutoLog_clicked(bool checked) { if(checked) ui_->prompt_to_log_check_box->setChecked(false); } void Configuration::impl::on_cbx2ToneSpacing_clicked(bool b) { if(b) ui_->cbx4ToneSpacing->setChecked(false); } void Configuration::impl::on_cbx4ToneSpacing_clicked(bool b) { if(b) ui_->cbx2ToneSpacing->setChecked(false); } void Configuration::impl::on_Field_Day_Exchange_textEdited (QString const& exchange) { auto text = exchange.simplified ().toUpper (); auto class_pos = text.indexOf (QRegularExpression {R"([A-H])"}); if (class_pos >= 0 && text.size () >= class_pos + 2 && text.at (class_pos + 1) != QChar {' '}) { text.insert (class_pos + 1, QChar {' '}); } ui_->Field_Day_Exchange->setText (text); } void Configuration::impl::on_RTTY_Exchange_textEdited (QString const& exchange) { ui_->RTTY_Exchange->setText (exchange.toUpper ()); } bool Configuration::impl::have_rig () { if (!open_rig ()) { MessageBox::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, [=] (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 current_offset_ = stations_.offset (f); cached_rig_state_.frequency (apply_calibration (f + current_offset_)); // qDebug () << "Configuration::impl::transceiver_frequency: n:" << transceiver_command_number_ + 1 << "f:" << 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 current_tx_offset_ = stations_.offset (f); cached_rig_state_.tx_frequency (apply_calibration (f + current_tx_offset_)); } // qDebug () << "Configuration::impl::transceiver_tx_frequency: n:" << transceiver_command_number_ + 1 << "f:" << 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); // qDebug () << "Configuration::impl::transceiver_mode: n:" << transceiver_command_number_ + 1 << "m:" << 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); // qDebug () << "Configuration::impl::transceiver_ptt: n:" << transceiver_command_number_ + 1 << "on:" << 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) { LOG_TRACE ("#: " << 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 (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 ()) - current_offset_); if (reported_state.tx_frequency ()) { // take off calibration & offset reported_state.tx_frequency (remove_calibration (reported_state.tx_frequency ()) - current_tx_offset_); } Q_EMIT self_->transceiver_update (reported_state); } } void Configuration::impl::handle_transceiver_failure (QString const& reason) { LOG_ERROR ("handle_transceiver_failure: reason: " << reason); close_rig (); ui_->test_PTT_push_button->setChecked (false); if (isVisible ()) { MessageBox::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 QAudioDeviceInfo Configuration::impl::find_audio_device (QAudio::Mode mode, QComboBox * combo_box , QString const& device_name) { using std::copy; using std::back_inserter; if (device_name.size ()) { Q_EMIT self_->enumerating_audio_devices (); auto const& devices = QAudioDeviceInfo::availableDevices (mode); Q_FOREACH (auto const& p, devices) { // qDebug () << "Configuration::impl::find_audio_device: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes (); if (p.deviceName () == device_name) { // convert supported channel counts into something we can store in the item model QList channel_counts; auto scc = p.supportedChannelCounts (); copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts)); combo_box->insertItem (0, device_name, QVariant::fromValue (audio_info_type {p, channel_counts})); combo_box->setCurrentIndex (0); return p; } } // insert a place holder for the not found device combo_box->insertItem (0, device_name + " (" + tr ("Not found", "audio device missing") + ")", QVariant::fromValue (audio_info_type {})); combo_box->setCurrentIndex (0); } return {}; } // load the available audio devices into the selection combo box void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * combo_box , QAudioDeviceInfo * device) { using std::copy; using std::back_inserter; combo_box->clear (); Q_EMIT self_->enumerating_audio_devices (); int current_index = -1; auto const& devices = QAudioDeviceInfo::availableDevices (mode); Q_FOREACH (auto const& p, devices) { // qDebug () << "Configuration::impl::load_audio_devices: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes (); // convert supported channel counts into something we can store in the item model QList channel_counts; auto scc = p.supportedChannelCounts (); copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts)); combo_box->addItem (p.deviceName (), QVariant::fromValue (audio_info_type {p, channel_counts})); if (p == *device) { current_index = combo_box->count () - 1; } } combo_box->setCurrentIndex (current_index); } // 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) { 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); } } } // get the select network interfaces from the selection combo box void Configuration::impl::validate_network_interfaces (QString const& /*text*/) { auto model = static_cast (ui_->udp_interfaces_combo_box->model ()); bool has_checked {false}; int loopback_row {-1}; for (int row = 0; row < model->rowCount (); ++row) { if (model->item (row)->data ().toString () == loopback_interface_name_) { loopback_row = row; } else if (Qt::Checked == model->item (row)->checkState ()) { has_checked = true; } } if (loopback_row >= 0) { if (!has_checked) { model->item (loopback_row)->setCheckState (Qt::Checked); } model->item (loopback_row)->setEnabled (has_checked); } } // get the select network interfaces from the selection combo box QStringList Configuration::impl::get_selected_network_interfaces (CheckableItemComboBox * combo_box) { QStringList interfaces; auto model = static_cast (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; } // enable only the channels that are supported by the selected audio device void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both) { // disable all items for (int i (0); i < combo_box->count (); ++i) { combo_box->setItemData (i, combo_box_item_disabled, Qt::UserRole - 1); } Q_FOREACH (QVariant const& v , (source_combo_box->itemData (index).value ().second)) { // enable valid options int n {v.toInt ()}; if (2 == n) { combo_box->setItemData (AudioDevice::Left, combo_box_item_enabled, Qt::UserRole - 1); combo_box->setItemData (AudioDevice::Right, combo_box_item_enabled, Qt::UserRole - 1); if (allow_both) { combo_box->setItemData (AudioDevice::Both, combo_box_item_enabled, Qt::UserRole - 1); } } else if (1 == n) { combo_box->setItemData (AudioDevice::Mono, combo_box_item_enabled, Qt::UserRole - 1); } } } void Configuration::impl::find_tab (QWidget * target) { for (auto * parent = target->parentWidget (); parent; parent = parent->parentWidget ()) { auto index = ui_->configuration_tabs->indexOf (parent); if (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;irig_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"(^\\\\\.\\)"})); auto tip = QString {"%1 %2 %3"}.arg (p.manufacturer ()).arg (p.serialNumber ()).arg (p.description ()).trimmed (); if (tip.size ()) { cb->setItemData (cb->count () - 1, tip, Qt::ToolTipRole); } } } cb->addItem ("USB"); cb->setItemData (cb->count () - 1, "Custom USB device", Qt::ToolTipRole); 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)); } ENUM_QDATASTREAM_OPS_IMPL (Configuration, DataMode); ENUM_QDATASTREAM_OPS_IMPL (Configuration, Type2MsgGen); ENUM_CONVERSION_OPS_IMPL (Configuration, DataMode); ENUM_CONVERSION_OPS_IMPL (Configuration, Type2MsgGen);