diff --git a/MultiSettings.cpp b/MultiSettings.cpp index f9c5300b7..698fcc8b8 100644 --- a/MultiSettings.cpp +++ b/MultiSettings.cpp @@ -27,6 +27,8 @@ #include #include +#include "SettingsGroup.hpp" + #include "pimpl_impl.hpp" namespace @@ -34,6 +36,8 @@ 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"; // calculate a useable and unique settings file path QString settings_path () @@ -77,7 +81,7 @@ namespace 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 ())}; + bool valid {!current_names.contains (name.trimmed ())}; button_box->button (QDialogButtonBox::Ok)->setEnabled (valid); }); connect (button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); @@ -155,15 +159,8 @@ private: // 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&); + void load_from (Dictionary const&, bool add_placeholder = true); // switch to this configuration void select_configuration (QMainWindow *); @@ -183,9 +180,11 @@ private: // 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 + QString current_; + + // action to take on restart + enum class RepositionType {unchanged, replace, save_and_replace} reposition_type_; + Dictionary new_settings_; bool exit_flag_; // false means loop around with new // configuration QActionGroup * configurations_group_; @@ -226,6 +225,7 @@ bool MultiSettings::exit () MultiSettings::impl::impl () : settings_ {settings_path (), QSettings::IniFormat} + , reposition_type_ {RepositionType::unchanged} , exit_flag_ {true} , configurations_group_ {new QActionGroup {this}} , select_action_ {new QAction {tr ("&Switch To"), this}} @@ -241,33 +241,93 @@ MultiSettings::impl::impl () throw std::runtime_error {QString {"Cannot access \"%1\" for writing"} .arg (settings_.fileName ()).toStdString ()}; } - current_ = settings_.value (multi_settings_current_group_key).toString (); - reposition (); + + // 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 + { + SettingsGroup alternatives {&settings_, multi_settings_root_group}; + current_ = settings_.value (multi_settings_current_name_key).toString (); + if (!current_.size ()) + { + current_ = tr (default_string); + settings_.setValue (multi_settings_current_name_key, current_); + } + } + settings_.sync (); } +// do actions that can only be done once all the windows are closed 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 + switch (reposition_type_) { - if (!available_.contains (current_)) + 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 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 ()) { - // insert new group name as it may not have been created yet - available_ << current_; + if (!key.contains (multi_settings_root_group)) + { + settings_.remove (key); + } } - // switch to the specified configuration - settings_.beginGroup (current_); - } - else - { - settings_.endGroup (); // back to root for default configuration + // insert the new settings + load_from (new_settings_, false); + // 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 (""); // 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; } + + reposition_type_ = RepositionType::unchanged; // reset bool exit {exit_flag_}; - exit_flag_ = true; // reset exit flag so normal exit works + exit_flag_ = true; // reset exit flag so normal exit works return exit; } @@ -276,20 +336,23 @@ bool MultiSettings::impl::reposition () // and, reset void MultiSettings::impl::create_menu_actions (QMainWindow * main_window, QMenu * menu) { + SettingsGroup alternatives {&settings_, multi_settings_root_group}; + // get the current configuration name + auto current_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, tr (default_string), configurations_group_); + QMenu * default_menu = create_sub_menu (menu, current_configuration_name, configurations_group_); + // and set as the current configuration + default_menu->menuAction ()->setChecked (true); + + QStringList available_configurations; + // get the existing alternatives + available_configurations = settings_.childGroups (); // add all the other configurations - for (auto const& configuration_name: available_) + for (auto const& configuration_name: available_configurations) { - QMenu * configuration_menu = create_sub_menu (menu, configuration_name, configurations_group_); - if (current_ == configuration_name) - { - default_menu = configuration_menu; - } + create_sub_menu (menu, configuration_name, configurations_group_); } - // 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) { @@ -322,12 +385,7 @@ bool MultiSettings::impl::exit () } action_connections_.clear (); - if (settings_.group ().size ()) // not default configuration - { - // back to the settings root - settings_.endGroup (); - settings_.endGroup (); - } + // do any configuration swap required and return exit flag return reposition (); } @@ -345,17 +403,17 @@ QMenu * MultiSettings::impl::create_sub_menu (QMenu * parent_menu, sub_menu->addAction (rename_action_); sub_menu->addAction (reset_action_); sub_menu->addAction (delete_action_); + + // disable disallowed actions before showing sub menu 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)}; + bool is_current {sub_menu->menuAction ()->text () == current_}; 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); + delete_action_->setEnabled (!is_current); + SettingsGroup alternatives {&settings_, multi_settings_root_group}; + clone_into_action_->setEnabled (settings_.childGroups ().size ()); active_sub_menu_ = sub_menu; }); + return sub_menu; } @@ -364,9 +422,8 @@ 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)) + // filter out multi settings group + if (!key.contains (multi_settings_root_group)) { settings[key] = settings_.value (key); } @@ -374,48 +431,43 @@ auto MultiSettings::impl::get_settings () const -> Dictionary return settings; } -void MultiSettings::impl::switch_to_root_group () +void MultiSettings::impl::load_from (Dictionary const& dictionary, bool add_placeholder) { - if (current_.size ()) + if (dictionary.size ()) { - settings_.endGroup (); + for (Dictionary::const_iterator iter = dictionary.constBegin (); + iter != dictionary.constEnd (); ++iter) + { + settings_.setValue (iter.key (), iter.value ()); + } } - else + else if (add_placeholder) { - 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 ()); + // 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 (QMainWindow * main_window) { if (active_sub_menu_) { - auto const& name = active_sub_menu_->title (); + auto const& target_name = active_sub_menu_->title (); - if (name != current_) + if (target_name != current_) { - current_ = tr (default_string) == name ? QString {} : name; + { + // position to the alternative settings + SettingsGroup alternatives {&settings_, multi_settings_root_group}; + // save the target settings + SettingsGroup target_group {&settings_, target_name}; + new_settings_ = get_settings (); + } + // and set up the restart + current_ = target_name; + reposition_type_ = RepositionType::save_and_replace; exit_flag_ = false; main_window->close (); } @@ -426,15 +478,24 @@ void MultiSettings::impl::clone_configuration (QMenu * menu) { if (active_sub_menu_) { - auto const& name = active_sub_menu_->title (); + auto const& source_name = active_sub_menu_->title (); - // grab the data to clone - Dictionary old_settings {get_settings ()}; - - switch_to_root_group (); + // 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 {name + " - Copy"};; + QString new_name_root {source_name + " - Copy"};; QString new_name {new_name_root}; unsigned index {0}; do @@ -442,14 +503,8 @@ void MultiSettings::impl::clone_configuration (QMenu * menu) 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_); + SettingsGroup new_group {&settings_, new_name}; + load_from (source_settings); // insert the new configuration sub menu in the parent menu create_sub_menu (menu, new_name, configurations_group_); @@ -460,17 +515,24 @@ void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window) { if (active_sub_menu_) { - auto const& name = active_sub_menu_->title (); + auto const& target_name = active_sub_menu_->title (); - switch_to_root_group (); + // 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 - QStringList sources {settings_.childGroups ()}; - if (name != tr (default_string)) { - sources.removeOne (name); - sources << tr (default_string); + // 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 ())) { @@ -478,57 +540,40 @@ void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window) 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 (target_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) + // grab the data to clone from + if (source_name == current_group_name) { - settings_.beginGroup (multi_settings_root_group); + // grab the data to clone from the current settings + new_settings_ = get_settings (); } else { - settings_.endGroup (); + SettingsGroup alternatives {&settings_, multi_settings_root_group}; + SettingsGroup source_group {&settings_, source_name}; + new_settings_ = get_settings (); } - // purge target settings - if (tr (default_string) == name) + // purge target settings and replace + if (target_name == current_) { - 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); - } - } + // restart with new settings + reposition_type_ = RepositionType::replace; + exit_flag_ = false; + main_window->close (); } else { - settings_.beginGroup (name); + SettingsGroup alternatives {&settings_, multi_settings_root_group}; + SettingsGroup target_group {&settings_, target_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 (); + load_from (new_settings_); + new_settings_.clear (); } } } - - switch_to_group (current_); } } @@ -536,39 +581,34 @@ void MultiSettings::impl::reset_configuration (QMainWindow * main_window) { if (active_sub_menu_) { - auto const& name = active_sub_menu_->title (); + auto const& target_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))) + .arg (target_name))) { return; } - switch_to_root_group (); - - if (tr (default_string) == name) + if (target_name == current_) { - 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); + // restart with default settings + reposition_type_ = RepositionType::replace; + new_settings_.clear (); + exit_flag_ = false; + main_window->close (); } else { - settings_.beginGroup (name); + SettingsGroup alternatives {&settings_, multi_settings_root_group}; + SettingsGroup target_group {&settings_, target_name}; settings_.remove (""); // purge entire group - settings_.endGroup (); + // add a placeholder to stop alternative configuration name + // being lost + settings_.setValue (multi_settings_place_holder_key, QVariant {}); + settings_.sync (); } - switch_to_group (current_); } } @@ -576,34 +616,41 @@ void MultiSettings::impl::rename_configuration (QMainWindow * main_window) { if (active_sub_menu_) { - auto const& name = active_sub_menu_->title (); + auto const& target_name = active_sub_menu_->title (); - switch_to_root_group (); + // 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 {name, settings_.childGroups (), main_window}; + NameDialog dialog {target_name, invalid_names, 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 (); - + if (target_name == current_) + { + settings_.setValue (multi_settings_current_name_key, dialog.new_name ()); + settings_.sync (); + current_ = dialog.new_name (); + } + 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 (""); // 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 active_sub_menu_->setTitle (dialog.new_name ()); } - - switch_to_group (current_); } } @@ -611,23 +658,28 @@ void MultiSettings::impl::delete_configuration (QMainWindow * main_window) { if (active_sub_menu_) { - auto const& name = active_sub_menu_->title (); + auto const& target_name = active_sub_menu_->title (); - if (QMessageBox::Yes != QMessageBox::question (main_window, - tr ("Delete Configuration"), - tr ("Confirm deletion of configuration \"%1\"?") - .arg (name))) + if (target_name == current_) { - return; + return; // suicide not allowed here } - - switch_to_root_group (); - - settings_.beginGroup (name); - settings_.remove (""); // purge entire group - settings_.endGroup (); - switch_to_group (current_); - + else + { + if (QMessageBox::Yes != QMessageBox::question (main_window, + tr ("Delete Configuration"), + tr ("Confirm deletion of configuration \"%1\"?") + .arg (target_name))) + { + return; + } + SettingsGroup alternatives {&settings_, multi_settings_root_group}; + SettingsGroup target_group {&settings_, target_name}; + // purge the configuration data + settings_.remove (""); // purge entire group + settings_.sync (); + } + // update the menu active_sub_menu_->deleteLater (), active_sub_menu_ = nullptr; } } diff --git a/MultiSettings.hpp b/MultiSettings.hpp index a3324de36..80e1b49a7 100644 --- a/MultiSettings.hpp +++ b/MultiSettings.hpp @@ -15,14 +15,12 @@ class QMenu; // 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. +// only QSettings object. The alternative settings are stored as +// QSettings groups which are children of a root level group called +// MultiSettings. The current 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: