mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
799 lines
29 KiB
C++
799 lines
29 KiB
C++
|
|
#include "MultiSettings.h"
|
||
|
|
|
||
|
|
#include <stdexcept>
|
||
|
|
|
||
|
|
#include <QAction>
|
||
|
|
#include <QActionGroup>
|
||
|
|
#include <QApplication>
|
||
|
|
#include <QComboBox>
|
||
|
|
#include <QDialog>
|
||
|
|
#include <QDialogButtonBox>
|
||
|
|
#include <QDir>
|
||
|
|
#include <QFont>
|
||
|
|
#include <QFormLayout>
|
||
|
|
#include <QLabel>
|
||
|
|
#include <QLineEdit>
|
||
|
|
#include <QList>
|
||
|
|
#include <QMainWindow>
|
||
|
|
#include <QMenu>
|
||
|
|
#include <QMetaObject>
|
||
|
|
#include <QObject>
|
||
|
|
#include <QPushButton>
|
||
|
|
#include <QRegularExpression>
|
||
|
|
#include <QRegularExpressionValidator>
|
||
|
|
#include <QSettings>
|
||
|
|
#include <QStandardPaths>
|
||
|
|
#include <QString>
|
||
|
|
#include <QStringList>
|
||
|
|
#include <QVBoxLayout>
|
||
|
|
|
||
|
|
#include "JS8MessageBox.h"
|
||
|
|
#include "JS8_Include/SettingsGroup.h"
|
||
|
|
#include "qt_helpers.h"
|
||
|
|
|
||
|
|
#include "JS8_Include/pimpl_impl.h"
|
||
|
|
|
||
|
|
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";
|
||
|
|
char const *multi_settings_current_name_key = "CurrentName";
|
||
|
|
char const *multi_settings_place_holder_key = "MultiSettingsPlaceHolder";
|
||
|
|
|
||
|
|
QString unescape_ampersands(QString s) { return s.replace("&&", "&"); }
|
||
|
|
|
||
|
|
// calculate a useable and unique settings file path
|
||
|
|
QString settings_path() {
|
||
|
|
auto const &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 {
|
||
|
|
Q_OBJECT
|
||
|
|
|
||
|
|
public:
|
||
|
|
explicit NameDialog(QString const ¤t_name,
|
||
|
|
QStringList const ¤t_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{!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 {
|
||
|
|
Q_OBJECT
|
||
|
|
|
||
|
|
public:
|
||
|
|
explicit ExistingNameDialog(QStringList const ¤t_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_;
|
||
|
|
};
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
class MultiSettings::impl final : public QObject {
|
||
|
|
Q_OBJECT
|
||
|
|
|
||
|
|
public:
|
||
|
|
explicit impl(MultiSettings const *parent, QString const &config_name);
|
||
|
|
bool reposition();
|
||
|
|
void create_menu_actions(QMainWindow *main_window, QMenu *menu);
|
||
|
|
bool exit();
|
||
|
|
|
||
|
|
QSettings settings_;
|
||
|
|
QString current_;
|
||
|
|
|
||
|
|
// switch to this configuration
|
||
|
|
void select_configuration(QString const &target_name);
|
||
|
|
|
||
|
|
private:
|
||
|
|
using Dictionary = QVariantMap;
|
||
|
|
|
||
|
|
// 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;
|
||
|
|
|
||
|
|
// write the settings values from the dictionary to the current group
|
||
|
|
void load_from(Dictionary const &, bool add_placeholder = true);
|
||
|
|
|
||
|
|
// clone this configuration
|
||
|
|
void clone_configuration(QMenu *, QMenu const *);
|
||
|
|
|
||
|
|
// update this configuration from another
|
||
|
|
void clone_into_configuration(QMenu const *);
|
||
|
|
|
||
|
|
// reset configuration to default values
|
||
|
|
void reset_configuration(QMenu const *);
|
||
|
|
|
||
|
|
// change configuration name
|
||
|
|
void rename_configuration(QMenu *);
|
||
|
|
|
||
|
|
// remove a configuration
|
||
|
|
void delete_configuration(QMenu *);
|
||
|
|
|
||
|
|
// action to take on restart
|
||
|
|
enum class RepositionType { unchanged, replace, save_and_replace };
|
||
|
|
void restart(RepositionType);
|
||
|
|
|
||
|
|
MultiSettings const *parent_; // required for emitting signals
|
||
|
|
QMainWindow *main_window_;
|
||
|
|
bool name_change_emit_pending_; // delayed until menu built
|
||
|
|
|
||
|
|
QFont original_font_;
|
||
|
|
|
||
|
|
RepositionType reposition_type_;
|
||
|
|
Dictionary new_settings_;
|
||
|
|
bool exit_flag_; // false means loop around with new
|
||
|
|
// configuration
|
||
|
|
QActionGroup *configurations_group_;
|
||
|
|
};
|
||
|
|
|
||
|
|
#include "MultiSettings.moc"
|
||
|
|
#include "moc_MultiSettings.cpp"
|
||
|
|
|
||
|
|
MultiSettings::MultiSettings(QString const &config_name)
|
||
|
|
: m_{this, config_name} {}
|
||
|
|
|
||
|
|
MultiSettings::~MultiSettings() {}
|
||
|
|
|
||
|
|
QSettings *MultiSettings::settings() { return &m_->settings_; }
|
||
|
|
|
||
|
|
QVariant MultiSettings::common_value(QString const &key,
|
||
|
|
QVariant const &default_value) const {
|
||
|
|
QVariant value;
|
||
|
|
QSettings *mutable_settings{const_cast<QSettings *>(&m_->settings_)};
|
||
|
|
auto const ¤t_group = mutable_settings->group();
|
||
|
|
if (current_group.size())
|
||
|
|
mutable_settings->endGroup();
|
||
|
|
{
|
||
|
|
SettingsGroup alternatives{mutable_settings, multi_settings_root_group};
|
||
|
|
value = mutable_settings->value(key, default_value);
|
||
|
|
}
|
||
|
|
if (current_group.size())
|
||
|
|
mutable_settings->beginGroup(current_group);
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::set_common_value(QString const &key,
|
||
|
|
QVariant const &value) {
|
||
|
|
auto const ¤t_group = m_->settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
m_->settings_.endGroup();
|
||
|
|
{
|
||
|
|
SettingsGroup alternatives{&m_->settings_, multi_settings_root_group};
|
||
|
|
m_->settings_.setValue(key, value);
|
||
|
|
}
|
||
|
|
if (current_group.size())
|
||
|
|
m_->settings_.beginGroup(current_group);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::remove_common_value(QString const &key) {
|
||
|
|
if (!key.size())
|
||
|
|
return; // we don't allow global delete as it
|
||
|
|
// would break this classes data model
|
||
|
|
auto const ¤t_group = m_->settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
m_->settings_.endGroup();
|
||
|
|
{
|
||
|
|
SettingsGroup alternatives{&m_->settings_, multi_settings_root_group};
|
||
|
|
m_->settings_.remove(key);
|
||
|
|
}
|
||
|
|
if (current_group.size())
|
||
|
|
m_->settings_.beginGroup(current_group);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::create_menu_actions(QMainWindow *main_window, QMenu *menu) {
|
||
|
|
m_->create_menu_actions(main_window, menu);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::select_configuration(QString const &name) {
|
||
|
|
m_->select_configuration(name);
|
||
|
|
}
|
||
|
|
|
||
|
|
QString MultiSettings::configuration_name() const { return m_->current_; }
|
||
|
|
|
||
|
|
bool MultiSettings::exit() { return m_->exit(); }
|
||
|
|
|
||
|
|
MultiSettings::impl::impl(MultiSettings const *parent,
|
||
|
|
QString const &config_name)
|
||
|
|
: settings_{settings_path(), QSettings::IniFormat}, parent_{parent},
|
||
|
|
main_window_{nullptr}, name_change_emit_pending_{true},
|
||
|
|
reposition_type_{RepositionType::unchanged}, exit_flag_{true},
|
||
|
|
configurations_group_{new QActionGroup{this}} {
|
||
|
|
if (!settings_.isWritable()) {
|
||
|
|
throw std::runtime_error{QString{"Cannot access \"%1\" for writing"}
|
||
|
|
.arg(settings_.fileName())
|
||
|
|
.toStdString()};
|
||
|
|
}
|
||
|
|
|
||
|
|
// deal with transient, now defunct, settings key
|
||
|
|
if (settings_.contains(multi_settings_current_group_key)) {
|
||
|
|
current_ = settings_.value(multi_settings_current_group_key).toString();
|
||
|
|
settings_.remove(multi_settings_current_group_key);
|
||
|
|
if (current_.size()) {
|
||
|
|
{
|
||
|
|
SettingsGroup alternatives{&settings_,
|
||
|
|
multi_settings_root_group};
|
||
|
|
{
|
||
|
|
SettingsGroup source_group{&settings_, current_};
|
||
|
|
new_settings_ = get_settings();
|
||
|
|
}
|
||
|
|
settings_.setValue(multi_settings_current_name_key,
|
||
|
|
tr(default_string));
|
||
|
|
}
|
||
|
|
reposition_type_ = RepositionType::save_and_replace;
|
||
|
|
reposition();
|
||
|
|
} else {
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
settings_.setValue(multi_settings_current_name_key,
|
||
|
|
tr(default_string));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// bootstrap
|
||
|
|
QStringList available_configurations;
|
||
|
|
{
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
available_configurations = settings_.childGroups();
|
||
|
|
// use last selected configuration
|
||
|
|
current_ = settings_.value(multi_settings_current_name_key).toString();
|
||
|
|
if (!current_.size()) {
|
||
|
|
// no configurations so use default name
|
||
|
|
current_ = tr(default_string);
|
||
|
|
settings_.setValue(multi_settings_current_name_key, current_);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (config_name.size() && available_configurations.contains(config_name) &&
|
||
|
|
config_name != current_) {
|
||
|
|
// switch to specified configuration
|
||
|
|
{
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
// save the target settings
|
||
|
|
SettingsGroup target_group{&settings_, config_name};
|
||
|
|
new_settings_ = get_settings();
|
||
|
|
}
|
||
|
|
current_ = config_name;
|
||
|
|
reposition_type_ = RepositionType::save_and_replace;
|
||
|
|
reposition();
|
||
|
|
}
|
||
|
|
settings_.sync();
|
||
|
|
}
|
||
|
|
|
||
|
|
// do actions that can only be done once all the windows are closed
|
||
|
|
bool MultiSettings::impl::reposition() {
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
switch (reposition_type_) {
|
||
|
|
case RepositionType::save_and_replace: {
|
||
|
|
// save the current settings with the other alternatives
|
||
|
|
Dictionary saved_settings{get_settings()};
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
// get the current configuration name
|
||
|
|
auto const &previous_group_name =
|
||
|
|
settings_.value(multi_settings_current_name_key, tr(default_string))
|
||
|
|
.toString();
|
||
|
|
SettingsGroup save_group{&settings_, previous_group_name};
|
||
|
|
load_from(saved_settings);
|
||
|
|
}
|
||
|
|
// fall through
|
||
|
|
case RepositionType::replace:
|
||
|
|
// and purge current settings
|
||
|
|
for (auto const &key : settings_.allKeys()) {
|
||
|
|
if (!key.contains(multi_settings_root_group)) {
|
||
|
|
settings_.remove(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// insert the new settings
|
||
|
|
load_from(new_settings_, false);
|
||
|
|
if (!new_settings_.size()) {
|
||
|
|
// if we are clearing the current settings then we must
|
||
|
|
// reset the application font and the font in the
|
||
|
|
// application style sheet, this is necessary since the
|
||
|
|
// application instance is not recreated
|
||
|
|
qApp->setFont(original_font_);
|
||
|
|
qApp->setStyleSheet(qApp->styleSheet() + "* {" +
|
||
|
|
font_as_stylesheet(original_font_) + '}');
|
||
|
|
}
|
||
|
|
// now we have set up the new current we can safely purge it
|
||
|
|
// from the alternatives
|
||
|
|
{
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
{
|
||
|
|
SettingsGroup purge_group{&settings_, current_};
|
||
|
|
settings_.remove(QString{}); // purge entire group
|
||
|
|
}
|
||
|
|
// switch to the specified configuration name
|
||
|
|
settings_.setValue(multi_settings_current_name_key, current_);
|
||
|
|
}
|
||
|
|
settings_.sync();
|
||
|
|
// fall through
|
||
|
|
case RepositionType::unchanged:
|
||
|
|
new_settings_.clear();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
|
||
|
|
reposition_type_ = RepositionType::unchanged; // reset
|
||
|
|
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) {
|
||
|
|
main_window_ = main_window;
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
// get the current configuration name
|
||
|
|
auto const ¤t_configuration_name =
|
||
|
|
settings_.value(multi_settings_current_name_key, tr(default_string))
|
||
|
|
.toString();
|
||
|
|
// add the default configuration sub menu
|
||
|
|
QMenu *default_menu = create_sub_menu(menu, current_configuration_name,
|
||
|
|
configurations_group_);
|
||
|
|
// and set as the current configuration
|
||
|
|
default_menu->menuAction()->setChecked(true);
|
||
|
|
|
||
|
|
// get the existing alternatives
|
||
|
|
auto const &available_configurations = settings_.childGroups();
|
||
|
|
|
||
|
|
// add all the other configurations
|
||
|
|
for (auto const &configuration_name : available_configurations) {
|
||
|
|
create_sub_menu(menu, configuration_name, configurations_group_);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
|
||
|
|
if (name_change_emit_pending_) {
|
||
|
|
Q_EMIT parent_->configurationNameChanged(unescape_ampersands(current_));
|
||
|
|
name_change_emit_pending_ = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 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() {
|
||
|
|
// ensure that configuration name changed signal gets fired on restart
|
||
|
|
name_change_emit_pending_ = true;
|
||
|
|
|
||
|
|
// do any configuration swap required and return exit flag
|
||
|
|
return reposition();
|
||
|
|
}
|
||
|
|
|
||
|
|
QMenu *MultiSettings::impl::create_sub_menu(QMenu *parent_menu,
|
||
|
|
QString const &menu_title,
|
||
|
|
QActionGroup *action_group) {
|
||
|
|
auto sub_menu = parent_menu->addMenu(menu_title);
|
||
|
|
if (action_group)
|
||
|
|
action_group->addAction(sub_menu->menuAction());
|
||
|
|
sub_menu->menuAction()->setCheckable(true);
|
||
|
|
|
||
|
|
// populate sub-menu actions before showing
|
||
|
|
connect(sub_menu, &QMenu::aboutToShow, [this, parent_menu, sub_menu]() {
|
||
|
|
// depopulate before populating and showing because on Mac OS X
|
||
|
|
// there is an issue with depopulating in QMenu::aboutToHide()
|
||
|
|
// with connections being disconnected before they are actioned
|
||
|
|
while (!sub_menu->actions().isEmpty()) {
|
||
|
|
sub_menu->removeAction(sub_menu->actions().last());
|
||
|
|
}
|
||
|
|
|
||
|
|
bool is_current{sub_menu->menuAction()->text() == current_};
|
||
|
|
if (!is_current) {
|
||
|
|
auto select_action = new QAction{tr("&Switch To"), this};
|
||
|
|
sub_menu->addAction(select_action);
|
||
|
|
connect(select_action, &QAction::triggered, [this, sub_menu](bool) {
|
||
|
|
select_configuration(sub_menu->title());
|
||
|
|
});
|
||
|
|
sub_menu->addSeparator();
|
||
|
|
}
|
||
|
|
|
||
|
|
auto clone_action = new QAction{tr("&Clone"), this};
|
||
|
|
sub_menu->addAction(clone_action);
|
||
|
|
connect(clone_action, &QAction::triggered,
|
||
|
|
[this, parent_menu, sub_menu](bool) {
|
||
|
|
clone_configuration(parent_menu, sub_menu);
|
||
|
|
});
|
||
|
|
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
if (settings_.childGroups().size()) {
|
||
|
|
auto clone_into_action = new QAction{tr("Clone &Into ..."), this};
|
||
|
|
sub_menu->addAction(clone_into_action);
|
||
|
|
connect(
|
||
|
|
clone_into_action, &QAction::triggered,
|
||
|
|
[this, sub_menu](bool) { clone_into_configuration(sub_menu); });
|
||
|
|
}
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
auto reset_action = new QAction{tr("R&eset"), this};
|
||
|
|
sub_menu->addAction(reset_action);
|
||
|
|
connect(reset_action, &QAction::triggered,
|
||
|
|
[this, sub_menu](bool) { reset_configuration(sub_menu); });
|
||
|
|
|
||
|
|
auto rename_action = new QAction{tr("&Rename ..."), this};
|
||
|
|
sub_menu->addAction(rename_action);
|
||
|
|
connect(rename_action, &QAction::triggered,
|
||
|
|
[this, sub_menu](bool) { rename_configuration(sub_menu); });
|
||
|
|
|
||
|
|
if (!is_current) {
|
||
|
|
auto delete_action = new QAction{tr("&Delete"), this};
|
||
|
|
sub_menu->addAction(delete_action);
|
||
|
|
connect(delete_action, &QAction::triggered,
|
||
|
|
[this, sub_menu](bool) { delete_configuration(sub_menu); });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return sub_menu;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto MultiSettings::impl::get_settings() const -> Dictionary {
|
||
|
|
Dictionary settings;
|
||
|
|
for (auto const &key : settings_.allKeys()) {
|
||
|
|
// filter out multi settings group and empty settings
|
||
|
|
// placeholder
|
||
|
|
if (!key.contains(multi_settings_root_group) &&
|
||
|
|
!key.contains(multi_settings_place_holder_key)) {
|
||
|
|
settings[key] = settings_.value(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::impl::load_from(Dictionary const &dictionary,
|
||
|
|
bool add_placeholder) {
|
||
|
|
if (dictionary.size()) {
|
||
|
|
for (Dictionary::const_iterator iter = dictionary.constBegin();
|
||
|
|
iter != dictionary.constEnd(); ++iter) {
|
||
|
|
settings_.setValue(iter.key(), iter.value());
|
||
|
|
}
|
||
|
|
} else if (add_placeholder) {
|
||
|
|
// add a placeholder key to stop the alternative configuration
|
||
|
|
// name from disappearing
|
||
|
|
settings_.setValue(multi_settings_place_holder_key, QVariant{});
|
||
|
|
}
|
||
|
|
settings_.sync();
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::impl::select_configuration(QString const &target_name) {
|
||
|
|
if (main_window_ && target_name != current_) {
|
||
|
|
bool changed{false};
|
||
|
|
{
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
// position to the alternative settings
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
if (settings_.childGroups().contains(target_name)) {
|
||
|
|
changed = true;
|
||
|
|
// save the target settings
|
||
|
|
SettingsGroup target_group{&settings_, target_name};
|
||
|
|
new_settings_ = get_settings();
|
||
|
|
}
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
}
|
||
|
|
if (changed) {
|
||
|
|
// and set up the restart
|
||
|
|
current_ = target_name;
|
||
|
|
Q_EMIT parent_->configurationNameChanged(
|
||
|
|
unescape_ampersands(current_));
|
||
|
|
restart(RepositionType::save_and_replace);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::impl::clone_configuration(QMenu *parent_menu,
|
||
|
|
QMenu const *menu) {
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
auto const &source_name = menu->title();
|
||
|
|
|
||
|
|
// settings to clone
|
||
|
|
Dictionary source_settings;
|
||
|
|
if (source_name == current_) {
|
||
|
|
// grab the data to clone from the current settings
|
||
|
|
source_settings = get_settings();
|
||
|
|
}
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
if (source_name != current_) {
|
||
|
|
SettingsGroup source_group{&settings_, source_name};
|
||
|
|
source_settings = get_settings();
|
||
|
|
}
|
||
|
|
|
||
|
|
// find a new unique name
|
||
|
|
QString new_name_root{source_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) ||
|
||
|
|
new_name == current_);
|
||
|
|
SettingsGroup new_group{&settings_, new_name};
|
||
|
|
load_from(source_settings);
|
||
|
|
|
||
|
|
// insert the new configuration sub menu in the parent menu
|
||
|
|
create_sub_menu(parent_menu, new_name, configurations_group_);
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::impl::clone_into_configuration(QMenu const *menu) {
|
||
|
|
Q_ASSERT(main_window_);
|
||
|
|
if (!main_window_)
|
||
|
|
return;
|
||
|
|
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
auto const &target_name = menu->title();
|
||
|
|
|
||
|
|
// get the current configuration name
|
||
|
|
QString current_group_name;
|
||
|
|
QStringList sources;
|
||
|
|
{
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
current_group_name =
|
||
|
|
settings_.value(multi_settings_current_name_key).toString();
|
||
|
|
|
||
|
|
{
|
||
|
|
// get the source configuration name for the clone
|
||
|
|
sources = settings_.childGroups();
|
||
|
|
sources << current_group_name;
|
||
|
|
sources.removeOne(target_name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// pick a source configuration
|
||
|
|
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 (main_window_ &&
|
||
|
|
JS8MessageBox::Yes ==
|
||
|
|
JS8MessageBox::query_message(
|
||
|
|
main_window_, tr("Clone Into Configuration"),
|
||
|
|
tr("Confirm overwrite of all values for configuration "
|
||
|
|
"\"%1\" with values from \"%2\"?")
|
||
|
|
.arg(unescape_ampersands(target_name))
|
||
|
|
.arg(unescape_ampersands(source_name)))) {
|
||
|
|
// grab the data to clone from
|
||
|
|
if (source_name == current_group_name) {
|
||
|
|
// grab the data to clone from the current settings
|
||
|
|
new_settings_ = get_settings();
|
||
|
|
} else {
|
||
|
|
SettingsGroup alternatives{&settings_,
|
||
|
|
multi_settings_root_group};
|
||
|
|
SettingsGroup source_group{&settings_, source_name};
|
||
|
|
new_settings_ = get_settings();
|
||
|
|
}
|
||
|
|
|
||
|
|
// purge target settings and replace
|
||
|
|
if (target_name == current_) {
|
||
|
|
// restart with new settings
|
||
|
|
restart(RepositionType::replace);
|
||
|
|
} else {
|
||
|
|
SettingsGroup alternatives{&settings_,
|
||
|
|
multi_settings_root_group};
|
||
|
|
SettingsGroup target_group{&settings_, target_name};
|
||
|
|
settings_.remove(QString{}); // purge entire group
|
||
|
|
load_from(new_settings_);
|
||
|
|
new_settings_.clear();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::impl::reset_configuration(QMenu const *menu) {
|
||
|
|
Q_ASSERT(main_window_);
|
||
|
|
if (!main_window_)
|
||
|
|
return;
|
||
|
|
|
||
|
|
auto const &target_name = menu->title();
|
||
|
|
|
||
|
|
if (!main_window_ ||
|
||
|
|
JS8MessageBox::Yes !=
|
||
|
|
JS8MessageBox::query_message(
|
||
|
|
main_window_, tr("Reset Configuration"),
|
||
|
|
tr("Confirm reset to default values for configuration \"%1\"?")
|
||
|
|
.arg(unescape_ampersands(target_name)))) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (target_name == current_) {
|
||
|
|
// restart with default settings
|
||
|
|
new_settings_.clear();
|
||
|
|
restart(RepositionType::replace);
|
||
|
|
} else {
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
SettingsGroup target_group{&settings_, target_name};
|
||
|
|
settings_.remove(QString{}); // purge entire group
|
||
|
|
// add a placeholder to stop alternative configuration name
|
||
|
|
// being lost
|
||
|
|
settings_.setValue(multi_settings_place_holder_key, QVariant{});
|
||
|
|
settings_.sync();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::impl::rename_configuration(QMenu *menu) {
|
||
|
|
Q_ASSERT(main_window_);
|
||
|
|
if (!main_window_)
|
||
|
|
return;
|
||
|
|
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
auto const &target_name = menu->title();
|
||
|
|
|
||
|
|
// gather names we cannot use
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
auto invalid_names = settings_.childGroups();
|
||
|
|
invalid_names
|
||
|
|
<< settings_.value(multi_settings_current_name_key).toString();
|
||
|
|
|
||
|
|
// get the new name
|
||
|
|
NameDialog dialog{target_name, invalid_names, main_window_};
|
||
|
|
if (QDialog::Accepted == dialog.exec()) {
|
||
|
|
if (target_name == current_) {
|
||
|
|
settings_.setValue(multi_settings_current_name_key,
|
||
|
|
dialog.new_name());
|
||
|
|
settings_.sync();
|
||
|
|
current_ = dialog.new_name();
|
||
|
|
Q_EMIT parent_->configurationNameChanged(
|
||
|
|
unescape_ampersands(current_));
|
||
|
|
} else {
|
||
|
|
// switch to the target group and fetch the configuration data
|
||
|
|
Dictionary target_settings;
|
||
|
|
{
|
||
|
|
// grab the target configuration settings
|
||
|
|
SettingsGroup target_group{&settings_, target_name};
|
||
|
|
target_settings = get_settings();
|
||
|
|
// purge the old configuration data
|
||
|
|
settings_.remove(QString{}); // purge entire group
|
||
|
|
}
|
||
|
|
// load into new configuration group name
|
||
|
|
SettingsGroup target_group{&settings_, dialog.new_name()};
|
||
|
|
load_from(target_settings);
|
||
|
|
}
|
||
|
|
// change the action text in the menu
|
||
|
|
menu->setTitle(dialog.new_name());
|
||
|
|
}
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::impl::delete_configuration(QMenu *menu) {
|
||
|
|
Q_ASSERT(main_window_);
|
||
|
|
auto const &target_name = menu->title();
|
||
|
|
|
||
|
|
if (target_name == current_) {
|
||
|
|
return; // suicide not allowed here
|
||
|
|
} else {
|
||
|
|
if (!main_window_ ||
|
||
|
|
JS8MessageBox::Yes !=
|
||
|
|
JS8MessageBox::query_message(
|
||
|
|
main_window_, tr("Delete Configuration"),
|
||
|
|
tr("Confirm deletion of configuration \"%1\"?")
|
||
|
|
.arg(unescape_ampersands(target_name)))) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
auto const ¤t_group = settings_.group();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.endGroup();
|
||
|
|
SettingsGroup alternatives{&settings_, multi_settings_root_group};
|
||
|
|
SettingsGroup target_group{&settings_, target_name};
|
||
|
|
// purge the configuration data
|
||
|
|
settings_.remove(QString{}); // purge entire group
|
||
|
|
settings_.sync();
|
||
|
|
if (current_group.size())
|
||
|
|
settings_.beginGroup(current_group);
|
||
|
|
}
|
||
|
|
// update the menu
|
||
|
|
menu->deleteLater();
|
||
|
|
}
|
||
|
|
|
||
|
|
void MultiSettings::impl::restart(RepositionType type) {
|
||
|
|
Q_ASSERT(main_window_);
|
||
|
|
reposition_type_ = type;
|
||
|
|
exit_flag_ = false;
|
||
|
|
main_window_->close();
|
||
|
|
main_window_ = nullptr;
|
||
|
|
}
|