diff --git a/CMakeLists.txt b/CMakeLists.txt index 92269630a..ca1a3cf9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,7 @@ set (wsjt_qt_CXXSRCS SampleDownloader/FileNode.cpp SampleDownloader/RemoteFile.cpp DisplayManual.cpp + MultiSettings.cpp ) set (wsjt_qtmm_CXXSRCS diff --git a/MultiSettings.cpp b/MultiSettings.cpp new file mode 100644 index 000000000..f9c5300b7 --- /dev/null +++ b/MultiSettings.cpp @@ -0,0 +1,633 @@ +#include "MultiSettings.hpp" + +#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" + +namespace +{ + char const * default_string = QT_TRANSLATE_NOOP ("MultiSettings", "Default"); + char const * multi_settings_root_group = "MultiSettings"; + char const * multi_settings_current_group_key = "CurrentMultiSettingsConfiguration"; + + // calculate a useable and unique settings file path + QString settings_path () + { + auto config_directory = QStandardPaths::writableLocation (QStandardPaths::ConfigLocation); + QDir config_path {config_directory}; // will be "." if config_directory is empty + if (!config_path.mkpath (".")) + { + throw std::runtime_error {"Cannot find a usable configuration path \"" + config_path.path ().toStdString () + '"'}; + } + return config_path.absoluteFilePath (QApplication::applicationName () + ".ini"); + } + + // + // Dialog to get a valid new configuration name + // + class NameDialog final + : public QDialog + { + public: + explicit NameDialog (QString const& current_name, + QStringList const& current_names, + QWidget * parent = nullptr) + : QDialog {parent} + { + setWindowTitle (tr ("New Configuration Name")); + + auto form_layout = new QFormLayout (); + form_layout->addRow (tr ("Old name:"), &old_name_label_); + old_name_label_.setText (current_name); + form_layout->addRow (tr ("&New name:"), &name_line_edit_); + + auto main_layout = new QVBoxLayout (this); + main_layout->addLayout (form_layout); + + auto button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel}; + button_box->button (QDialogButtonBox::Ok)->setEnabled (false); + main_layout->addWidget (button_box); + + auto * name_validator = new QRegularExpressionValidator {QRegularExpression {R"([^/\\]+)"}, this}; + name_line_edit_.setValidator (name_validator); + + connect (&name_line_edit_, &QLineEdit::textChanged, [current_names, button_box] (QString const& name) { + bool valid {name.trimmed () != tr (default_string) && !current_names.contains (name.trimmed ())}; + button_box->button (QDialogButtonBox::Ok)->setEnabled (valid); + }); + connect (button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect (button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + } + + QString new_name () const + { + return name_line_edit_.text ().trimmed (); + } + + private: + QLabel old_name_label_; + QLineEdit name_line_edit_; + }; + + // + // Dialog to get a valid new existing name + // + class ExistingNameDialog final + : public QDialog + { + public: + explicit ExistingNameDialog (QStringList const& current_names, QWidget * parent = nullptr) + : QDialog {parent} + { + setWindowTitle (tr ("Configuration to Clone From")); + + name_combo_box_.addItems (current_names); + + auto form_layout = new QFormLayout (); + form_layout->addRow (tr ("&Source Configuration Name:"), &name_combo_box_); + + 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, &QDialog::accept); + connect (button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + } + + QString name () const + { + return name_combo_box_.currentText (); + } + + private: + QComboBox name_combo_box_; + }; +} + +class MultiSettings::impl final + : public QObject +{ + Q_OBJECT + +public: + explicit impl (); + bool reposition (); + void create_menu_actions (QMainWindow * main_window, QMenu * menu); + bool exit (); + + QSettings settings_; + +private: + using Dictionary = QMap; + + // create a configuration maintenance sub menu + QMenu * create_sub_menu (QMenu * parent, + QString const& menu_title, + QActionGroup * = nullptr); + + // extract all settings from the current QSettings group + Dictionary get_settings () const; + + // leave the current group and move to the multi settings root group + void switch_to_root_group (); + + // starting from he multi settings root group, switch back to the + // current group + void switch_to_group (QString const& group_name); + + // write the settings values from the dictionary to the current group + void load_from (Dictionary const&); + + // switch to this configuration + void select_configuration (QMainWindow *); + + // clone this configuration + void clone_configuration (QMenu *); + + // update this configuration from another + void clone_into_configuration (QMainWindow *); + + // reset configuration to default values + void reset_configuration (QMainWindow *); + + // change configuration name + void rename_configuration (QMainWindow *); + + // remove a configuration + void delete_configuration (QMainWindow *); + + QString current_; // current/new configuration or empty for default + QStringList available_; // all non-default configurations + // including new one + bool exit_flag_; // false means loop around with new + // configuration + QActionGroup * configurations_group_; + QAction * select_action_; + QAction * clone_action_; + QAction * clone_into_action_; + QAction * reset_action_; + QAction * rename_action_; + QAction * delete_action_; + QList action_connections_; + QMenu * active_sub_menu_; +}; + +#include "MultiSettings.moc" + +MultiSettings::MultiSettings () +{ +} + +MultiSettings::~MultiSettings () +{ +} + +QSettings * MultiSettings::settings () +{ + return &m_->settings_; +} + +void MultiSettings::create_menu_actions (QMainWindow * main_window, QMenu * menu) +{ + m_->create_menu_actions (main_window, menu); +} + +bool MultiSettings::exit () +{ + return m_->exit (); +} + +MultiSettings::impl::impl () + : settings_ {settings_path (), QSettings::IniFormat} + , exit_flag_ {true} + , configurations_group_ {new QActionGroup {this}} + , select_action_ {new QAction {tr ("&Switch To"), this}} + , clone_action_ {new QAction {tr ("&Clone"), this}} + , clone_into_action_ {new QAction {tr ("Clone &Into ..."), this}} + , reset_action_ {new QAction {tr ("R&eset"), this}} + , rename_action_ {new QAction {tr ("&Rename ..."), this}} + , delete_action_ {new QAction {tr ("&Delete"), this}} + , active_sub_menu_ {nullptr} +{ + if (!settings_.isWritable ()) + { + throw std::runtime_error {QString {"Cannot access \"%1\" for writing"} + .arg (settings_.fileName ()).toStdString ()}; + } + current_ = settings_.value (multi_settings_current_group_key).toString (); + reposition (); +} + +bool MultiSettings::impl::reposition () +{ + // save new current and reposition settings + // assumes settings are positioned at the root + settings_.setValue (multi_settings_current_group_key, current_); + settings_.beginGroup (multi_settings_root_group); + available_ = settings_.childGroups (); + if (current_.size ()) // new configuration is not the default + { + if (!available_.contains (current_)) + { + // insert new group name as it may not have been created yet + available_ << current_; + } + // switch to the specified configuration + settings_.beginGroup (current_); + } + else + { + settings_.endGroup (); // back to root for default configuration + } + bool exit {exit_flag_}; + exit_flag_ = true; // reset exit flag so normal exit works + return exit; +} + +// populate a pop up menu with the configurations sub-menus for +// maintenance including select, clone, clone from, delete, rename +// and, reset +void MultiSettings::impl::create_menu_actions (QMainWindow * main_window, QMenu * menu) +{ + // add the default configuration sub menu + QMenu * default_menu = create_sub_menu (menu, tr (default_string), configurations_group_); + + // add all the other configurations + for (auto const& configuration_name: available_) + { + QMenu * configuration_menu = create_sub_menu (menu, configuration_name, configurations_group_); + if (current_ == configuration_name) + { + default_menu = configuration_menu; + } + } + // and set the current configuration + default_menu->menuAction ()->setChecked (true); + + // hook up configuration actions + action_connections_ << connect (select_action_, &QAction::triggered, [this, main_window] (bool) { + select_configuration (main_window); + }); + action_connections_ << connect (clone_action_, &QAction::triggered, [this, menu] (bool) { + clone_configuration (menu); + }); + action_connections_ << connect (clone_into_action_, &QAction::triggered, [this, main_window] (bool) { + clone_into_configuration (main_window); + }); + action_connections_ << connect (rename_action_, &QAction::triggered, [this, main_window] (bool) { + rename_configuration (main_window); + }); + action_connections_ << connect (reset_action_, &QAction::triggered, [this, main_window] (bool) { + reset_configuration (main_window); + }); + action_connections_ << connect (delete_action_, &QAction::triggered, [this, main_window] (bool) { + delete_configuration (main_window); + }); +} + +// call this at the end of the main program loop to determine if the +// main window really wants to quit or to run again with a new configuration +bool MultiSettings::impl::exit () +{ + for (auto const& connection: action_connections_) + { + disconnect (connection); + } + action_connections_.clear (); + + if (settings_.group ().size ()) // not default configuration + { + // back to the settings root + settings_.endGroup (); + settings_.endGroup (); + } + return reposition (); +} + +QMenu * MultiSettings::impl::create_sub_menu (QMenu * parent_menu, + QString const& menu_title, + QActionGroup * action_group) +{ + QMenu * sub_menu = parent_menu->addMenu (menu_title); + if (action_group) action_group->addAction (sub_menu->menuAction ()); + sub_menu->menuAction ()->setCheckable (true); + sub_menu->addAction (select_action_); + sub_menu->addSeparator (); + sub_menu->addAction (clone_action_); + sub_menu->addAction (clone_into_action_); + sub_menu->addAction (rename_action_); + sub_menu->addAction (reset_action_); + sub_menu->addAction (delete_action_); + connect (sub_menu, &QMenu::aboutToShow, [this, sub_menu] () { + bool is_default {sub_menu->menuAction ()->text () == tr (default_string)}; + bool is_current {sub_menu->menuAction ()->text () == current_ + || (current_.isEmpty () && is_default)}; + select_action_->setEnabled (!is_current); + clone_into_action_->setEnabled (!is_current); + rename_action_->setEnabled (!is_current && !is_default); + reset_action_->setEnabled (!is_current); + delete_action_->setEnabled (!is_default && !is_current); + active_sub_menu_ = sub_menu; + }); + return sub_menu; +} + +auto MultiSettings::impl::get_settings () const -> Dictionary +{ + Dictionary settings; + for (auto const& key: settings_.allKeys ()) + { + // filter out multi settings keys + if (!key.contains (multi_settings_current_group_key) + && !key.contains (multi_settings_root_group)) + { + settings[key] = settings_.value (key); + } + } + return settings; +} + +void MultiSettings::impl::switch_to_root_group () +{ + if (current_.size ()) + { + settings_.endGroup (); + } + else + { + settings_.beginGroup (multi_settings_root_group); + } +} + +void MultiSettings::impl::switch_to_group (QString const& group_name) +{ + if (group_name.size () && group_name != tr (default_string)) + { + settings_.beginGroup (group_name); + } + else + { + settings_.endGroup (); // back to root for default + } +} + +void MultiSettings::impl::load_from (Dictionary const& dictionary) +{ + for (Dictionary::const_iterator iter = dictionary.constBegin (); + iter != dictionary.constEnd (); ++iter) + { + settings_.setValue (iter.key (), iter.value ()); + } +} + +void MultiSettings::impl::select_configuration (QMainWindow * main_window) +{ + if (active_sub_menu_) + { + auto const& name = active_sub_menu_->title (); + + if (name != current_) + { + current_ = tr (default_string) == name ? QString {} : name; + exit_flag_ = false; + main_window->close (); + } + } +} + +void MultiSettings::impl::clone_configuration (QMenu * menu) +{ + if (active_sub_menu_) + { + auto const& name = active_sub_menu_->title (); + + // grab the data to clone + Dictionary old_settings {get_settings ()}; + + switch_to_root_group (); + + // find a new unique name + QString new_name_root {name + " - Copy"};; + QString new_name {new_name_root}; + unsigned index {0}; + do + { + if (index++) new_name = new_name_root + '(' + QString::number (index) + ')'; + } + while (settings_.childGroups ().contains (new_name)); + settings_.beginGroup (new_name); + + // Clone the settings + load_from (old_settings); + + // switch back to current group + settings_.endGroup (); + switch_to_group (current_); + + // insert the new configuration sub menu in the parent menu + create_sub_menu (menu, new_name, configurations_group_); + } +} + +void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window) +{ + if (active_sub_menu_) + { + auto const& name = active_sub_menu_->title (); + + switch_to_root_group (); + + // get the source configuration name for the clone + QStringList sources {settings_.childGroups ()}; + if (name != tr (default_string)) + { + sources.removeOne (name); + sources << tr (default_string); + } + ExistingNameDialog dialog {sources, main_window}; + if (sources.size () && (1 == sources.size () || QDialog::Accepted == dialog.exec ())) + { + QString source_name {1 == sources.size () ? sources.at (0) : dialog.name ()}; + if (QMessageBox::Yes == QMessageBox::question (main_window, + tr ("Clone Into Configuration"), + tr ("Confirm overwrite of all values for configuration \"%1\" with values from \"%2\"?") + .arg (name) + .arg (source_name))) + { + // grab the data to clone + switch_to_group (source_name); + Dictionary clone_settings {get_settings ()}; + + if (tr (default_string) == source_name) + { + settings_.beginGroup (multi_settings_root_group); + } + else + { + settings_.endGroup (); + } + + // purge target settings + if (tr (default_string) == name) + { + settings_.endGroup (); + // piecemeal reset for default configuration + for (auto const& key: settings_.allKeys ()) + { + if (!key.contains (multi_settings_current_group_key) + && !key.contains (multi_settings_root_group)) + { + settings_.remove (key); + } + } + } + else + { + settings_.beginGroup (name); + settings_.remove (""); // purge entire group + } + + // load the settings + load_from (clone_settings); + + if (tr (default_string) == name) + { + settings_.beginGroup (multi_settings_root_group); + } + else + { + settings_.endGroup (); + } + } + } + + switch_to_group (current_); + } +} + +void MultiSettings::impl::reset_configuration (QMainWindow * main_window) +{ + if (active_sub_menu_) + { + auto const& name = active_sub_menu_->title (); + + if (QMessageBox::Yes != QMessageBox::question (main_window, + tr ("Reset Configuration"), + tr ("Confirm reset to default values for configuration \"%1\"?") + .arg (name))) + { + return; + } + + switch_to_root_group (); + + if (tr (default_string) == name) + { + settings_.endGroup (); + // piecemeal reset for default configuration + for (auto const& key: settings_.allKeys ()) + { + if (!key.contains (multi_settings_current_group_key) + && !key.contains (multi_settings_root_group)) + { + settings_.remove (key); + } + } + settings_.beginGroup (multi_settings_root_group); + } + else + { + settings_.beginGroup (name); + settings_.remove (""); // purge entire group + settings_.endGroup (); + } + switch_to_group (current_); + } +} + +void MultiSettings::impl::rename_configuration (QMainWindow * main_window) +{ + if (active_sub_menu_) + { + auto const& name = active_sub_menu_->title (); + + switch_to_root_group (); + + // get the new name + NameDialog dialog {name, settings_.childGroups (), main_window}; + if (QDialog::Accepted == dialog.exec ()) + { + // switch to the target group and fetch the configuration data + settings_.beginGroup (name); + + // Clone the settings + Dictionary target_settings {get_settings ()}; + settings_.endGroup (); + settings_.beginGroup (dialog.new_name ()); + load_from (target_settings); + + // purge the old configuration data + settings_.endGroup (); + settings_.beginGroup (name); + settings_.remove (""); // purge entire group + settings_.endGroup (); + + // change the action text in the menu + active_sub_menu_->setTitle (dialog.new_name ()); + } + + switch_to_group (current_); + } +} + +void MultiSettings::impl::delete_configuration (QMainWindow * main_window) +{ + if (active_sub_menu_) + { + auto const& name = active_sub_menu_->title (); + + if (QMessageBox::Yes != QMessageBox::question (main_window, + tr ("Delete Configuration"), + tr ("Confirm deletion of configuration \"%1\"?") + .arg (name))) + { + return; + } + + switch_to_root_group (); + + settings_.beginGroup (name); + settings_.remove (""); // purge entire group + settings_.endGroup (); + switch_to_group (current_); + + active_sub_menu_->deleteLater (), active_sub_menu_ = nullptr; + } +} diff --git a/MultiSettings.hpp b/MultiSettings.hpp new file mode 100644 index 000000000..a3324de36 --- /dev/null +++ b/MultiSettings.hpp @@ -0,0 +1,87 @@ +#ifndef MULTISETTINGS_HPP_ + +#include "pimpl_h.hpp" + +class QSettings; +class QMainWindow; +class QMenu; + +// +// MultiSettings - Manage multiple configuration names +// +// Responsibilities: +// +// MultiSettings allows a Qt application to be run with alternative +// settings as stored in a QSettings INI style file. As far as the +// application is concerned it uses the QSettings instance returned +// by the MultiSettings::settings() method as if it were the one and +// only QSettings object. The only caution is the the application +// must not end the outer settings group since the alternative +// settings are actually maintained as QSettings groups which are +// children of a root level group called MultiSettings. The default +// settings are themselves stored at the root so the QSettings group +// name MultiSettings is reserved. Also at the root level a key +// called CurrentMultiSettingsConfiguration is reserved to store the +// current configuration name. +// +// +// Example Usage: +// +// #include +// #include "MultiSettings.hpp" +// #include "MyMainWindow.hpp" +// +// int main (int argc, char * argv[]) { +// QApplication a {argc, argv}; +// MultiSettings multi_settings; +// int result; +// do { +// MyMainWindow main_window {&multi_settings}; +// main_window.show (); +// result = a.exec (); +// } while (!result && !multi_settings.exit ()); +// return result; +// } +// +// In the main window call MultiSettings::create_menu_actions() to +// populate an existing QMenu widget with the configuration switching +// and maintenance actions. This would normally be done in the main +// window class constructor: +// +// MyMainWindow::MyMainWindow (MultiSettings * multi_settings) { +// QSettings * settings {multi_settings->settings ()}; +// // ... +// multi_settings->create_menu_actions (this, ui->configurations_menu); +// // ... +// } +// + +class MultiSettings +{ +public: + explicit MultiSettings (); + MultiSettings (MultiSettings const&) = delete; + MultiSettings& operator = (MultiSettings const&) = delete; + ~MultiSettings (); + + // Add multiple configurations navigation and maintenance actions to + // a provided menu. The provided main window object instance will + // have its close() function called when a "Switch To" configuration + // action is triggered. + void create_menu_actions (QMainWindow *, QMenu *); + + // Access to the QSettings object instance. + QSettings * settings (); + + // Call this to determine if the application is terminating, if it + // returns false then the application main window should be + // recreated, shown and the application exec() function called + // again. + bool exit (); + +private: + class impl; + pimpl m_; +}; + +#endif diff --git a/main.cpp b/main.cpp index 36ed7213f..d010036c3 100644 --- a/main.cpp +++ b/main.cpp @@ -29,6 +29,7 @@ #include "MetaDataRegistry.hpp" #include "SettingsGroup.hpp" #include "TraceFile.hpp" +#include "MultiSettings.hpp" #include "mainwindow.h" #include "commons.h" #include "lib/init_random_seed.h" @@ -186,81 +187,77 @@ int main(int argc, char *argv[]) } #endif - auto config_directory = QStandardPaths::writableLocation (QStandardPaths::ConfigLocation); - QDir config_path {config_directory}; // will be "." if config_directory is empty - if (!config_path.mkpath (".")) - { - throw std::runtime_error {"Cannot find a usable configuration path \"" + config_path.path ().toStdString () + '"'}; - } - - auto settings_file = config_path.absoluteFilePath (a.applicationName () + ".ini"); - QSettings settings(settings_file, QSettings::IniFormat); - if (!settings.isWritable ()) - { - throw std::runtime_error {QString {"Cannot access \"%1\" for writing"}.arg (settings_file).toStdString ()}; - } + MultiSettings multi_settings; #if WSJT_QDEBUG_TO_FILE // Open a trace file TraceFile trace_file {QDir {QStandardPaths::writableLocation (QStandardPaths::TempLocation)}.absoluteFilePath (a.applicationName () + "_trace.log")}; - - // announce to trace file and dump settings qDebug () << program_title (revision ()) + " - Program startup"; - qDebug () << "++++++++++++++++++++++++++++ Settings ++++++++++++++++++++++++++++"; - for (auto const& key: settings.allKeys ()) +#endif + + int result; + do { - auto const& value = settings.value (key); - if (value.canConvert ()) +#if WSJT_QDEBUG_TO_FILE + // announce to trace file and dump settings + qDebug () << "++++++++++++++++++++++++++++ Settings ++++++++++++++++++++++++++++"; + for (auto const& key: multi_settings.settings ()->allKeys ()) { - auto const sequence = value.value (); - qDebug ().nospace () << key << ": "; - for (auto const& item: sequence) + auto const& value = multi_settings.settings ()->value (key); + if (value.canConvert ()) { - qDebug ().nospace () << '\t' << item; + auto const sequence = value.value (); + qDebug ().nospace () << key << ": "; + for (auto const& item: sequence) + { + qDebug ().nospace () << '\t' << item; + } + } + else + { + qDebug ().nospace () << key << ": " << value; } } - else - { - qDebug ().nospace () << key << ": " << value; + qDebug () << "---------------------------- Settings ----------------------------"; +#endif + + // Create and initialize shared memory segment + // Multiple instances: use rig_name as shared memory key + mem_jt9.setKey(a.applicationName ()); + + if(!mem_jt9.attach()) { + if (!mem_jt9.create(sizeof(struct dec_data))) { + QMessageBox::critical (nullptr, "Error", "Unable to create shared memory segment."); + exit(1); } - } - qDebug () << "---------------------------- Settings ----------------------------"; -#endif + } + memset(mem_jt9.data(),0,sizeof(struct dec_data)); //Zero all decoding params in shared memory - // Create and initialize shared memory segment - // Multiple instances: use rig_name as shared memory key - mem_jt9.setKey(a.applicationName ()); + unsigned downSampleFactor; + { + SettingsGroup {multi_settings.settings (), "Tune"}; - if(!mem_jt9.attach()) { - if (!mem_jt9.create(sizeof(struct dec_data))) { - QMessageBox::critical (nullptr, "Error", "Unable to create shared memory segment."); - exit(1); - } - } - memset(mem_jt9.data(),0,sizeof(struct dec_data)); //Zero all decoding params in shared memory - - unsigned downSampleFactor; - { - SettingsGroup {&settings, "Tune"}; - - // deal with Windows Vista and earlier input audio rate - // converter problems - downSampleFactor = settings.value ("Audio/DisableInputResampling", + // deal with Windows Vista and earlier input audio rate + // converter problems + downSampleFactor = multi_settings.settings ()->value ("Audio/DisableInputResampling", #if defined (Q_OS_WIN) - // default to true for - // Windows Vista and older - QSysInfo::WV_VISTA >= QSysInfo::WindowsVersion ? true : false + // default to true for + // Windows Vista and older + QSysInfo::WV_VISTA >= QSysInfo::WindowsVersion ? true : false #else - false + false #endif - ).toBool () ? 1u : 4u; - } + ).toBool () ? 1u : 4u; + } - // run the application UI - MainWindow w(multiple, &settings, &mem_jt9, downSampleFactor, new QNetworkAccessManager {&a}); - w.show(); - QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit())); - return a.exec(); + // run the application UI + MainWindow w(multiple, &multi_settings, &mem_jt9, downSampleFactor, new QNetworkAccessManager {&a}); + w.show(); + QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit())); + result = a.exec(); + } + while (!result && !multi_settings.exit ()); + return result; } catch (std::exception const& e) { diff --git a/mainwindow.cpp b/mainwindow.cpp index 60361a5d6..073fd930a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "revision_utils.hpp" #include "qt_helpers.hpp" @@ -47,6 +49,7 @@ #include "HelpTextWindow.hpp" #include "SampleDownloader.hpp" #include "Audio/BWFFile.hpp" +#include "MultiSettings.hpp" #include "ui_mainwindow.h" #include "moc_mainwindow.cpp" @@ -135,22 +138,24 @@ namespace } //--------------------------------------------------- MainWindow constructor -MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdmem, +MainWindow::MainWindow(bool multiple, MultiSettings * multi_settings, + QSharedMemory *shdmem, unsigned downSampleFactor, QNetworkAccessManager * network_manager, QWidget *parent) : QMainWindow(parent), m_dataDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}, m_revision {revision ()}, m_multiple {multiple}, - m_settings {settings}, + m_multi_settings {multi_settings}, + m_settings {multi_settings->settings ()}, ui(new Ui::MainWindow), - m_config {settings, this}, - m_WSPR_band_hopping {settings, &m_config, this}, + m_config {m_settings, this}, + m_WSPR_band_hopping {m_settings, &m_config, this}, m_WSPR_tx_next {false}, - m_wideGraph (new WideGraph(settings)), - m_echoGraph (new EchoGraph(settings)), - m_fastGraph (new FastGraph(settings)), - m_logDlg (new LogQSO (program_title (), settings, this)), + m_wideGraph (new WideGraph(m_settings)), + m_echoGraph (new EchoGraph(m_settings)), + m_fastGraph (new FastGraph(m_settings)), + m_logDlg (new LogQSO (program_title (), m_settings, this)), m_lastDialFreq {0}, //m_dialFreq {std::numeric_limits::max ()}, m_detector {new Detector {RX_SAMPLE_RATE, NTMAX, 6912 / 2, downSampleFactor}}, @@ -406,6 +411,9 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server); connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port); + // set up configurations menu + m_multi_settings->create_menu_actions (this, ui->menuConfig); + // set up message text validators ui->tx1->setValidator (new QRegExpValidator {message_alphabet, this}); ui->tx2->setValidator (new QRegExpValidator {message_alphabet, this}); diff --git a/mainwindow.h b/mainwindow.h index 626436587..82714c0fc 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -71,6 +71,7 @@ class Modulator; class SoundInput; class Detector; class SampleDownloader; +class MultiSettings; class MainWindow : public QMainWindow { @@ -82,7 +83,7 @@ public: using Mode = Modes::Mode; // Multiple instances: call MainWindow() with *thekey - explicit MainWindow(bool multiple, QSettings *, QSharedMemory *shdmem, + explicit MainWindow(bool multiple, MultiSettings *, QSharedMemory *shdmem, unsigned downSampleFactor, QNetworkAccessManager * network_manager, QWidget *parent = 0); ~MainWindow(); @@ -279,8 +280,8 @@ private: QDir m_dataDir; QString m_revision; bool m_multiple; + MultiSettings * m_multi_settings; QSettings * m_settings; - Ui::MainWindow * ui; // other windows diff --git a/mainwindow.ui b/mainwindow.ui index 07dc1a3ee..d159a6a7d 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -2375,7 +2375,13 @@ QPushButton[state="ok"] { + + + Configurations + + +