mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-04 16:31:17 -05:00
4cebdddfe5
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
634 lines
19 KiB
C++
634 lines
19 KiB
C++
#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;
|
|
}
|
|
}
|