#define SETTINGS_INTERFACE_IMPLEMENTATION #include #include #include #include #include #include #include #include #include #include #include namespace scwx { namespace qt { namespace settings { static const std::string logPrefix_ = "scwx::qt::settings::settings_interface"; template class SettingsInterface::Impl { public: explicit Impl() { context_->moveToThread(QCoreApplication::instance()->thread()); } ~Impl() {} void UpdateEditWidget(); void UpdateResetButton(); SettingsVariable* variable_ {nullptr}; bool stagedValid_ {true}; std::unique_ptr context_ {std::make_unique()}; QWidget* editWidget_ {nullptr}; QAbstractButton* resetButton_ {nullptr}; std::function mapFromValue_ {nullptr}; std::function mapToValue_ {nullptr}; }; template SettingsInterface::SettingsInterface() : p(std::make_unique()) { } template SettingsInterface::~SettingsInterface() = default; template SettingsInterface::SettingsInterface(SettingsInterface&&) noexcept = default; template SettingsInterface& SettingsInterface::operator=(SettingsInterface&&) noexcept = default; template void SettingsInterface::SetSettingsVariable(SettingsVariable& variable) { p->variable_ = &variable; } template void SettingsInterface::SetEditWidget(QWidget* widget) { if (p->editWidget_ != nullptr) { QObject::disconnect(p->editWidget_, nullptr, p->context_.get(), nullptr); } p->editWidget_ = widget; if (QLineEdit* lineEdit = dynamic_cast(widget)) { if constexpr (std::is_same_v) { // If the line is edited (not programatically changed), stage the new // value QObject::connect(lineEdit, &QLineEdit::textEdited, p->context_.get(), [this](const QString& text) { // Map to value if required std::string value {text.toStdString()}; if (p->mapToValue_ != nullptr) { value = p->mapToValue_(value); } // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); // TODO: Display invalid status }); } else if constexpr (std::is_same_v>) { // If the line is edited (not programatically changed), stage the new // value QObject::connect( lineEdit, &QLineEdit::textEdited, p->context_.get(), [this](const QString& text) { // Map to value if required T value {}; if (p->mapToValue_ != nullptr) { // User-defined map to value value = p->mapToValue_(text.toStdString()); } else { // Tokenize string to parse each element const std::string str {text.toStdString()}; boost::tokenizer tokens(str); for (auto it = tokens.begin(); it != tokens.end(); ++it) { try { // Good value value.push_back( static_cast(std::stoll(*it))); } catch (const std::exception&) { // Error value value.push_back( std::numeric_limits::min()); } } } // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); // TODO: Display invalid status }); } } else if (QCheckBox* checkBox = dynamic_cast(widget)) { if constexpr (std::is_same_v) { QObject::connect(checkBox, &QCheckBox::toggled, p->context_.get(), [this](bool checked) { // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(checked); p->UpdateResetButton(); }); } } else if (QComboBox* comboBox = dynamic_cast(widget)) { if constexpr (std::is_same_v) { QObject::connect(comboBox, &QComboBox::currentTextChanged, p->context_.get(), [this](const QString& text) { // Map to value if required std::string value {text.toStdString()}; if (p->mapToValue_ != nullptr) { value = p->mapToValue_(value); } // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); }); } } else if (QSpinBox* spinBox = dynamic_cast(widget)) { if constexpr (std::is_integral_v) { const std::optional minimum = p->variable_->GetMinimum(); const std::optional maximum = p->variable_->GetMaximum(); if (minimum.has_value()) { spinBox->setMinimum(static_cast(*minimum)); } if (maximum.has_value()) { spinBox->setMaximum(static_cast(*maximum)); } // If the spin box is edited, stage a changed value QObject::connect( spinBox, &QSpinBox::valueChanged, p->context_.get(), [this](int i) { const T value = p->variable_->GetValue(); const std::optional staged = p->variable_->GetStaged(); // If there is a value staged, and the new value is the same as // the current value, reset the staged value if (staged.has_value() && static_cast(i) == value) { p->variable_->Reset(); p->stagedValid_ = true; p->UpdateResetButton(); } // If there is no staged value, or if the new value is different // than what is staged, attempt to stage the value else if (!staged.has_value() || static_cast(i) != *staged) { p->stagedValid_ = p->variable_->StageValue(static_cast(i)); p->UpdateResetButton(); } // Otherwise, don't process an unchanged value }); } } p->UpdateEditWidget(); } template void SettingsInterface::SetResetButton(QAbstractButton* button) { if (p->resetButton_ != nullptr) { QObject::disconnect(p->resetButton_, nullptr, p->context_.get(), nullptr); } p->resetButton_ = button; QObject::connect(p->resetButton_, &QAbstractButton::clicked, p->context_.get(), [this]() { T defaultValue = p->variable_->GetDefault(); if (p->variable_->GetValue() == defaultValue) { // If the current value is default, reset the staged // value p->variable_->Reset(); p->stagedValid_ = true; p->UpdateEditWidget(); p->UpdateResetButton(); } else { // Stage the default value p->stagedValid_ = p->variable_->StageValue(defaultValue); p->UpdateEditWidget(); p->UpdateResetButton(); } }); p->UpdateResetButton(); } template void SettingsInterface::SetMapFromValueFunction( std::function function) { p->mapFromValue_ = function; } template void SettingsInterface::SetMapToValueFunction( std::function function) { p->mapToValue_ = function; } template void SettingsInterface::Impl::UpdateEditWidget() { // Use the staged value if present, otherwise the current value const std::optional staged = variable_->GetStaged(); const T value = variable_->GetValue(); const T& currentValue = staged.has_value() ? *staged : value; if (QLineEdit* lineEdit = dynamic_cast(editWidget_)) { if constexpr (std::is_integral_v) { lineEdit->setText(QString::number(currentValue)); } else if constexpr (std::is_same_v) { if (mapFromValue_ != nullptr) { lineEdit->setText( QString::fromStdString(mapFromValue_(currentValue))); } else { lineEdit->setText(QString::fromStdString(currentValue)); } } else if constexpr (std::is_same_v>) { if (mapFromValue_ != nullptr) { lineEdit->setText( QString::fromStdString(mapFromValue_(currentValue))); } else { lineEdit->setText(QString::fromStdString( fmt::format("{}", fmt::join(currentValue, ", ")))); } } } else if (QCheckBox* checkBox = dynamic_cast(editWidget_)) { if constexpr (std::is_same_v) { checkBox->setChecked(currentValue); } } else if (QComboBox* comboBox = dynamic_cast(editWidget_)) { if constexpr (std::is_same_v) { if (mapFromValue_ != nullptr) { comboBox->setCurrentText( QString::fromStdString(mapFromValue_(currentValue))); } else { comboBox->setCurrentText(QString::fromStdString(currentValue)); } } } else if (QSpinBox* spinBox = dynamic_cast(editWidget_)) { if constexpr (std::is_integral_v) { spinBox->setValue(static_cast(currentValue)); } } } template void SettingsInterface::Impl::UpdateResetButton() { const std::optional staged = variable_->GetStaged(); const T defaultValue = variable_->GetDefault(); const T value = variable_->GetValue(); if (resetButton_ != nullptr) { if (staged.has_value()) { resetButton_->setVisible(!stagedValid_ || *staged != defaultValue); } else { resetButton_->setVisible(value != defaultValue); } } } } // namespace settings } // namespace qt } // namespace scwx