mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 17:30:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			364 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			364 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #define SETTINGS_VARIABLE_IMPLEMENTATION
 | |
| 
 | |
| #include <scwx/qt/settings/settings_variable.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| 
 | |
| #include <optional>
 | |
| 
 | |
| #include <boost/json.hpp>
 | |
| #include <fmt/ostream.h>
 | |
| #include <QAbstractButton>
 | |
| #include <QCoreApplication>
 | |
| #include <QLineEdit>
 | |
| #include <QWidget>
 | |
| 
 | |
| 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 T>
 | |
| class SettingsVariable<T>::Impl
 | |
| {
 | |
| public:
 | |
|    explicit Impl()
 | |
|    {
 | |
|       context_->moveToThread(QCoreApplication::instance()->thread());
 | |
|    }
 | |
| 
 | |
|    ~Impl() {}
 | |
| 
 | |
|    void UpdateEditWidget();
 | |
|    void UpdateResetButton();
 | |
| 
 | |
|    T                             value_ {};
 | |
|    T                             default_ {};
 | |
|    std::optional<T>              staged_ {};
 | |
|    std::optional<T>              minimum_ {};
 | |
|    std::optional<T>              maximum_ {};
 | |
|    std::function<bool(const T&)> validator_ {nullptr};
 | |
| 
 | |
|    bool stagedValid_ {true};
 | |
| 
 | |
|    std::unique_ptr<QObject> context_ {std::make_unique<QObject>()};
 | |
|    QWidget*                 editWidget_ {nullptr};
 | |
|    QAbstractButton*         resetButton_ {nullptr};
 | |
| };
 | |
| 
 | |
| template<class T>
 | |
| SettingsVariable<T>::SettingsVariable(const std::string& name) :
 | |
|     SettingsVariableBase(name), p(std::make_unique<Impl>())
 | |
| {
 | |
| }
 | |
| template<class T>
 | |
| SettingsVariable<T>::~SettingsVariable() = default;
 | |
| 
 | |
| template<class T>
 | |
| SettingsVariable<T>::SettingsVariable(SettingsVariable&&) noexcept = default;
 | |
| template<class T>
 | |
| SettingsVariable<T>&
 | |
| SettingsVariable<T>::operator=(SettingsVariable&&) noexcept = default;
 | |
| 
 | |
| template<class T>
 | |
| inline auto FormatParameter(const T& value)
 | |
| {
 | |
|    if constexpr (std::is_integral_v<T> || std::is_same_v<T, std::string>)
 | |
|    {
 | |
|       return value;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       return fmt::join(value, ", ");
 | |
|    }
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| T SettingsVariable<T>::GetValue() const
 | |
| {
 | |
|    return p->value_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::SetValue(const T& value)
 | |
| {
 | |
|    bool validated = false;
 | |
| 
 | |
|    if (Validate(value))
 | |
|    {
 | |
|       p->value_ = value;
 | |
|       validated = true;
 | |
|    }
 | |
| 
 | |
|    return validated;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::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<T>(value),
 | |
|                     FormatParameter<T>(*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<T>(value),
 | |
|                     FormatParameter<T>(*p->maximum_));
 | |
|       p->value_ = *p->maximum_;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       logger_->warn("{} validation failed ({}), setting to default: {}",
 | |
|                     name(),
 | |
|                     FormatParameter<T>(value),
 | |
|                     FormatParameter<T>(p->default_));
 | |
|       p->value_ = p->default_;
 | |
|    }
 | |
| 
 | |
|    return validated;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetValueToDefault()
 | |
| {
 | |
|    p->value_ = p->default_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::StageValue(const T& value)
 | |
| {
 | |
|    if (Validate(value))
 | |
|    {
 | |
|       p->staged_      = value;
 | |
|       p->stagedValid_ = true;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       p->stagedValid_ = false;
 | |
|    }
 | |
| 
 | |
|    p->UpdateResetButton();
 | |
| 
 | |
|    return p->stagedValid_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::Commit()
 | |
| {
 | |
|    if (p->staged_.has_value())
 | |
|    {
 | |
|       p->value_ = std::move(*p->staged_);
 | |
|       p->staged_.reset();
 | |
|       p->stagedValid_ = true;
 | |
|    }
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::Reset()
 | |
| {
 | |
|    p->staged_.reset();
 | |
|    p->stagedValid_ = true;
 | |
| 
 | |
|    p->UpdateEditWidget();
 | |
|    p->UpdateResetButton();
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| T SettingsVariable<T>::GetDefault() const
 | |
| {
 | |
|    return p->default_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetDefault(const T& value)
 | |
| {
 | |
|    p->default_ = value;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetMinimum(const T& value)
 | |
| {
 | |
|    p->minimum_ = value;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetMaximum(const T& value)
 | |
| {
 | |
|    p->maximum_ = value;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetValidator(std::function<bool(const T&)> validator)
 | |
| {
 | |
|    p->validator_ = validator;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::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<class T>
 | |
| bool SettingsVariable<T>::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<T>(*jv));
 | |
|       }
 | |
|       catch (const std::exception& ex)
 | |
|       {
 | |
|          logger_->warn("{} is invalid ({}), setting to default: {}",
 | |
|                        name(),
 | |
|                        ex.what(),
 | |
|                        FormatParameter<T>(p->default_));
 | |
|          p->value_ = p->default_;
 | |
|       }
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       logger_->debug("{} is not present, setting to default: {}",
 | |
|                      name(),
 | |
|                      FormatParameter<T>(p->default_));
 | |
|       p->value_ = p->default_;
 | |
|    }
 | |
| 
 | |
|    return validated;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::WriteValue(boost::json::object& json) const
 | |
| {
 | |
|    json[name()] = boost::json::value_from<T&>(p->value_);
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetEditWidget(QWidget* widget)
 | |
| {
 | |
|    p->editWidget_ = widget;
 | |
| 
 | |
|    if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget))
 | |
|    {
 | |
|       if constexpr (std::is_same_v<T, std::string>)
 | |
|       {
 | |
|          // 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());
 | |
|                           });
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    p->UpdateEditWidget();
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::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<class T>
 | |
| void SettingsVariable<T>::Impl::UpdateEditWidget()
 | |
| {
 | |
|    // Use the staged value if present, otherwise the current value
 | |
|    T& value = staged_.has_value() ? *staged_ : value_;
 | |
| 
 | |
|    if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editWidget_))
 | |
|    {
 | |
|       if constexpr (std::is_integral_v<T>)
 | |
|       {
 | |
|          lineEdit->setText(QString::number(value));
 | |
|       }
 | |
|       else if constexpr (std::is_same_v<T, std::string>)
 | |
|       {
 | |
|          lineEdit->setText(QString::fromStdString(value));
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::Impl::UpdateResetButton()
 | |
| {
 | |
|    if (resetButton_ != nullptr)
 | |
|    {
 | |
|       if (staged_.has_value())
 | |
|       {
 | |
|          resetButton_->setVisible(!stagedValid_ || *staged_ != default_);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          resetButton_->setVisible(value_ != default_);
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::Equals(const SettingsVariableBase& o) const
 | |
| {
 | |
|    // This is only ever called with SettingsVariable<T>, so static_cast is safe
 | |
|    const SettingsVariable<T>& v = static_cast<const SettingsVariable<T>&>(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
 | 
