mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-11-04 05:50:31 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			832 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			832 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "MultiSettings.hpp"
 | 
						|
 | 
						|
#include <stdexcept>
 | 
						|
 | 
						|
#include <QObject>
 | 
						|
#include <QSettings>
 | 
						|
#include <QString>
 | 
						|
#include <QStringList>
 | 
						|
#include <QDir>
 | 
						|
#include <QFont>
 | 
						|
#include <QApplication>
 | 
						|
#include <QStandardPaths>
 | 
						|
#include <QMainWindow>
 | 
						|
#include <QMenu>
 | 
						|
#include <QAction>
 | 
						|
#include <QActionGroup>
 | 
						|
#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 "SettingsGroup.hpp"
 | 
						|
#include "qt_helpers.hpp"
 | 
						|
#include "SettingsGroup.hpp"
 | 
						|
#include "widgets/MessageBox.hpp"
 | 
						|
 | 
						|
#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";
 | 
						|
  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& 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 {!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& 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 (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 = 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;
 | 
						|
 | 
						|
  // 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& current_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& current_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& current_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& current_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& current_group = settings_.group ();
 | 
						|
  if (current_group.size ()) settings_.endGroup ();
 | 
						|
  SettingsGroup alternatives {&settings_, multi_settings_root_group};
 | 
						|
  // get the current configuration name
 | 
						|
  auto const& 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, 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& current_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& current_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& current_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& current_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_
 | 
						|
          && MessageBox::Yes == MessageBox::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_
 | 
						|
      || MessageBox::Yes != MessageBox::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& current_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& current_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_
 | 
						|
          || MessageBox::Yes != MessageBox::query_message (main_window_,
 | 
						|
                                                           tr ("Delete Configuration"),
 | 
						|
                                                           tr ("Confirm deletion of configuration \"%1\"?")
 | 
						|
                                                           .arg (unescape_ampersands (target_name))))
 | 
						|
        {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
      auto const& current_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;
 | 
						|
}
 |