#define SETTINGS_VARIABLE_IMPLEMENTATION #include #include #include #include #include #include #include #include #include namespace scwx { namespace qt { namespace settings { static const std::string logPrefix_ = "scwx::qt::settings::settings_variable"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); template class SettingsVariable::Impl { public: explicit Impl() { context_->moveToThread(QCoreApplication::instance()->thread()); } ~Impl() {} void UpdateEditWidget(); void UpdateResetButton(); T value_ {}; T default_ {}; std::optional staged_ {}; std::optional minimum_ {}; std::optional maximum_ {}; std::function validator_ {nullptr}; bool stagedValid_ {true}; std::unique_ptr context_ {std::make_unique()}; QWidget* editWidget_ {nullptr}; QAbstractButton* resetButton_ {nullptr}; }; template SettingsVariable::SettingsVariable(const std::string& name) : SettingsVariableBase(name), p(std::make_unique()) { } template SettingsVariable::~SettingsVariable() = default; template SettingsVariable::SettingsVariable(SettingsVariable&&) noexcept = default; template SettingsVariable& SettingsVariable::operator=(SettingsVariable&&) noexcept = default; template inline auto FormatParameter(const T& value) { if constexpr (std::is_integral_v || std::is_same_v) { return value; } else { return fmt::join(value, ", "); } } template T SettingsVariable::GetValue() const { return p->value_; } template bool SettingsVariable::SetValue(const T& value) { bool validated = false; if (Validate(value)) { p->value_ = value; validated = true; } return validated; } template bool SettingsVariable::SetValueOrDefault(const T& value) { bool validated = false; if (Validate(value)) { p->value_ = value; validated = true; } else if (p->minimum_.has_value() && value < p->minimum_) { logger_->warn("{0} less than minimum ({1} < {2}), setting to: {2}", name(), FormatParameter(value), FormatParameter(*p->minimum_)); p->value_ = *p->minimum_; } else if (p->maximum_.has_value() && value > p->maximum_) { logger_->warn("{0} greater than maximum ({1} > {2}), setting to: {2}", name(), FormatParameter(value), FormatParameter(*p->maximum_)); p->value_ = *p->maximum_; } else { logger_->warn("{} validation failed ({}), setting to default: {}", name(), FormatParameter(value), FormatParameter(p->default_)); p->value_ = p->default_; } return validated; } template void SettingsVariable::SetValueToDefault() { p->value_ = p->default_; } template bool SettingsVariable::StageValue(const T& value) { if (Validate(value)) { p->staged_ = value; p->stagedValid_ = true; } else { p->stagedValid_ = false; } p->UpdateResetButton(); return p->stagedValid_; } template void SettingsVariable::Commit() { if (p->staged_.has_value()) { p->value_ = std::move(*p->staged_); p->staged_.reset(); p->stagedValid_ = true; } } template void SettingsVariable::Reset() { p->staged_.reset(); p->stagedValid_ = true; p->UpdateEditWidget(); p->UpdateResetButton(); } template T SettingsVariable::GetDefault() const { return p->default_; } template void SettingsVariable::SetDefault(const T& value) { p->default_ = value; } template void SettingsVariable::SetMinimum(const T& value) { p->minimum_ = value; } template std::optional SettingsVariable::GetMinimum() const { return p->minimum_; } template void SettingsVariable::SetMaximum(const T& value) { p->maximum_ = value; } template std::optional SettingsVariable::GetMaximum() const { return p->maximum_; } template void SettingsVariable::SetValidator(std::function validator) { p->validator_ = validator; } template bool SettingsVariable::Validate(const T& value) const { return ( (!p->minimum_.has_value() || value >= p->minimum_) && // Validate minimum (!p->maximum_.has_value() || value <= p->maximum_) && // Validate maximum (p->validator_ == nullptr || p->validator_(value))); // User-validation } template bool SettingsVariable::ReadValue(const boost::json::object& json) { const boost::json::value* jv = json.if_contains(name()); bool validated = false; if (jv != nullptr) { try { validated = SetValueOrDefault(boost::json::value_to(*jv)); } catch (const std::exception& ex) { logger_->warn("{} is invalid ({}), setting to default: {}", name(), ex.what(), FormatParameter(p->default_)); p->value_ = p->default_; } } else { logger_->debug("{} is not present, setting to default: {}", name(), FormatParameter(p->default_)); p->value_ = p->default_; } return validated; } template void SettingsVariable::WriteValue(boost::json::object& json) const { json[name()] = boost::json::value_from(p->value_); } template void SettingsVariable::SetEditWidget(QWidget* widget) { 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) { // Attempt to stage the value StageValue(text.toStdString()); }); } } else if (QSpinBox* spinBox = dynamic_cast(widget)) { if constexpr (std::is_integral_v) { if (p->minimum_.has_value()) { spinBox->setMinimum(static_cast(*p->minimum_)); } if (p->maximum_.has_value()) { spinBox->setMaximum(static_cast(*p->maximum_)); } // If the spin box is edited, stage a changed value QObject::connect(spinBox, &QSpinBox::valueChanged, p->context_.get(), [this](int i) { // If there is a value staged, and the new value is // the same as the current value, reset the staged // value if (p->staged_.has_value() && static_cast(i) == p->value_) { Reset(); } // If there is no staged value, or if the new value // is different than what is staged, attempt to // stage the value else if (!p->staged_.has_value() || static_cast(i) != *p->staged_) { StageValue(static_cast(i)); } // Otherwise, don't process an unchanged value }); } } p->UpdateEditWidget(); } template void SettingsVariable::SetResetButton(QAbstractButton* button) { p->resetButton_ = button; QObject::connect(p->resetButton_, &QAbstractButton::clicked, p->context_.get(), [this]() { if (p->value_ == p->default_) { // If the current value is default, reset the staged // value Reset(); } else { // Stage the default value StageValue(p->default_); p->UpdateEditWidget(); } }); p->UpdateResetButton(); } template void SettingsVariable::Impl::UpdateEditWidget() { // Use the staged value if present, otherwise the current value T& value = staged_.has_value() ? *staged_ : value_; if (QLineEdit* lineEdit = dynamic_cast(editWidget_)) { if constexpr (std::is_integral_v) { lineEdit->setText(QString::number(value)); } else if constexpr (std::is_same_v) { lineEdit->setText(QString::fromStdString(value)); } } else if (QSpinBox* spinBox = dynamic_cast(editWidget_)) { if constexpr (std::is_integral_v) { spinBox->setValue(static_cast(value)); } } } template void SettingsVariable::Impl::UpdateResetButton() { if (resetButton_ != nullptr) { if (staged_.has_value()) { resetButton_->setVisible(!stagedValid_ || *staged_ != default_); } else { resetButton_->setVisible(value_ != default_); } } } template bool SettingsVariable::Equals(const SettingsVariableBase& o) const { // This is only ever called with SettingsVariable, so static_cast is safe const SettingsVariable& v = static_cast&>(o); // Don't compare validator return SettingsVariableBase::Equals(o) && // p->value_ == v.p->value_ && // p->default_ == v.p->default_ && // p->staged_ == v.p->staged_ && // p->minimum_ == v.p->minimum_ && // p->maximum_ == v.p->maximum_; } } // namespace settings } // namespace qt } // namespace scwx