mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-22 04:11:16 -05:00
Add multiple configurations navigation and management
Multiple configurations are accessed and maintained from a new main window menu bar pop up menu "Configurations". The prior settings are the "Default" entry. New configurations may be added by cloning existing ones. Maintenance and navigation is via sub menus for each configuration. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@6623 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
This commit is contained in:
parent
c40c824540
commit
4cebdddfe5
@ -217,6 +217,7 @@ set (wsjt_qt_CXXSRCS
|
||||
SampleDownloader/FileNode.cpp
|
||||
SampleDownloader/RemoteFile.cpp
|
||||
DisplayManual.cpp
|
||||
MultiSettings.cpp
|
||||
)
|
||||
|
||||
set (wsjt_qtmm_CXXSRCS
|
||||
|
633
MultiSettings.cpp
Normal file
633
MultiSettings.cpp
Normal file
@ -0,0 +1,633 @@
|
||||
#include "MultiSettings.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QDir>
|
||||
#include <QApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QMainWindow>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QMessageBox>
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionValidator>
|
||||
#include <QFormLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QList>
|
||||
#include <QMetaObject>
|
||||
|
||||
#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<QString, QVariant>;
|
||||
|
||||
// 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<QMetaObject::Connection> 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;
|
||||
}
|
||||
}
|
87
MultiSettings.hpp
Normal file
87
MultiSettings.hpp
Normal file
@ -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 <QApplication>
|
||||
// #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<impl> m_;
|
||||
};
|
||||
|
||||
#endif
|
113
main.cpp
113
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<QVariantList> ())
|
||||
#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<QSequentialIterable> ();
|
||||
qDebug ().nospace () << key << ": ";
|
||||
for (auto const& item: sequence)
|
||||
auto const& value = multi_settings.settings ()->value (key);
|
||||
if (value.canConvert<QVariantList> ())
|
||||
{
|
||||
qDebug ().nospace () << '\t' << item;
|
||||
auto const sequence = value.value<QSequentialIterable> ();
|
||||
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)
|
||||
{
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include <QVector>
|
||||
#include <QCursor>
|
||||
#include <QToolTip>
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
|
||||
#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<Radio::Frequency>::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});
|
||||
|
@ -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
|
||||
|
@ -2375,7 +2375,13 @@ QPushButton[state="ok"] {
|
||||
<addaction name="actionISCAT"/>
|
||||
<addaction name="actionJTMSK"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuConfig">
|
||||
<property name="title">
|
||||
<string>Configurations</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuConfig"/>
|
||||
<addaction name="menuView"/>
|
||||
<addaction name="menuMode"/>
|
||||
<addaction name="menuDecode"/>
|
||||
|
Loading…
Reference in New Issue
Block a user