Settings variable abstract base, additional validation, and reading/writing JSON

This commit is contained in:
Dan Paulat 2022-12-17 00:38:20 -06:00
parent 1ad67de71b
commit 46c4dd3780
7 changed files with 408 additions and 17 deletions

View file

@ -111,12 +111,14 @@ set(HDR_SETTINGS source/scwx/qt/settings/general_settings.hpp
source/scwx/qt/settings/map_settings.hpp source/scwx/qt/settings/map_settings.hpp
source/scwx/qt/settings/palette_settings.hpp source/scwx/qt/settings/palette_settings.hpp
source/scwx/qt/settings/settings_container.hpp source/scwx/qt/settings/settings_container.hpp
source/scwx/qt/settings/settings_variable.hpp) source/scwx/qt/settings/settings_variable.hpp
source/scwx/qt/settings/settings_variable_base.hpp)
set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp
source/scwx/qt/settings/map_settings.cpp source/scwx/qt/settings/map_settings.cpp
source/scwx/qt/settings/palette_settings.cpp source/scwx/qt/settings/palette_settings.cpp
source/scwx/qt/settings/settings_container.cpp source/scwx/qt/settings/settings_container.cpp
source/scwx/qt/settings/settings_variable.cpp) source/scwx/qt/settings/settings_variable.cpp
source/scwx/qt/settings/settings_variable_base.cpp)
set(HDR_TYPES source/scwx/qt/types/qt_types.hpp set(HDR_TYPES source/scwx/qt/types/qt_types.hpp
source/scwx/qt/types/radar_product_record.hpp source/scwx/qt/types/radar_product_record.hpp
source/scwx/qt/types/text_event_key.hpp) source/scwx/qt/types/text_event_key.hpp)

View file

@ -11,6 +11,7 @@ namespace settings
{ {
static const std::string logPrefix_ = "scwx::qt::settings::settings_container"; static const std::string logPrefix_ = "scwx::qt::settings::settings_container";
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
template<class Container> template<class Container>
class SettingsContainer<Container>::Impl class SettingsContainer<Container>::Impl
@ -61,16 +62,29 @@ bool SettingsContainer<Container>::SetValueOrDefault(const Container& c)
} }
else if (p->elementMinimum_.has_value() && value < p->elementMinimum_) else if (p->elementMinimum_.has_value() && value < p->elementMinimum_)
{ {
logger_->warn("{0} less than minimum ({1} < {2}), setting to: {2}",
this->name(),
value,
*p->elementMinimum_);
validated = false; validated = false;
return *p->elementMinimum_; return *p->elementMinimum_;
} }
else if (p->elementMaximum_.has_value() && value > p->elementMaximum_) else if (p->elementMaximum_.has_value() && value > p->elementMaximum_)
{ {
logger_->warn(
"{0} greater than maximum ({1} > {2}), setting to: {2}",
this->name(),
value,
*p->elementMaximum_);
validated = false; validated = false;
return *p->elementMaximum_; return *p->elementMaximum_;
} }
else else
{ {
logger_->warn("{} validation failed ({}), setting to default: {}",
this->name(),
value,
p->elementDefault_);
validated = false; validated = false;
return p->elementDefault_; return p->elementDefault_;
} }
@ -143,6 +157,21 @@ bool SettingsContainer<Container>::ValidateElement(const T& value) const
(p->elementValidator_ == nullptr || p->elementValidator_(value))); (p->elementValidator_ == nullptr || p->elementValidator_(value)));
} }
template<class Container>
bool SettingsContainer<Container>::Equals(const SettingsVariableBase& o) const
{
// This is only ever called with SettingsContainer<Container>, so static_cast
// is safe
const SettingsContainer<Container>& v =
static_cast<const SettingsContainer<Container>&>(o);
// Don't compare validator
return SettingsVariable<Container>::Equals(o) &&
p->elementDefault_ == v.p->elementDefault_ &&
p->elementMinimum_ == v.p->elementMinimum_ &&
p->elementMaximum_ == v.p->elementMaximum_;
}
} // namespace settings } // namespace settings
} // namespace qt } // namespace qt
} // namespace scwx } // namespace scwx

View file

@ -24,25 +24,83 @@ public:
SettingsContainer(SettingsContainer&&) noexcept; SettingsContainer(SettingsContainer&&) noexcept;
SettingsContainer& operator=(SettingsContainer&&) noexcept; SettingsContainer& operator=(SettingsContainer&&) noexcept;
/**
* Sets the current value of the settings variable. If the value is out of
* range, the value is set to the minimum or maximum. If the value fails
* validation, the value is set to default.
*
* @param c Container value to set
*
* @return true if the value is valid, false if the value was modified.
*/
bool SetValueOrDefault(const Container& c) override; bool SetValueOrDefault(const Container& c) override;
/**
* Validate the container value against the defined parameters of the
* settings variable.
*
* @param c Container value to validate
*
* @return true if the value is valid, false if the value failed validation.
*/
bool Validate(const Container& c) const override; bool Validate(const Container& c) const override;
/**
* Validate the element value against the defined parameters of the settings
* variable.
*
* @param value Element value to validate
*
* @return true if the value is valid, false if the value failed validation.
*/
bool ValidateElement(const T& value) const; bool ValidateElement(const T& value) const;
/**
* Gets the default element value of the settings variable.
*
* @return Default element value
*/
T GetElementDefault() const; T GetElementDefault() const;
/**
* Sets the default element value of the settings variable.
*
* @param value Default element value
*/
void SetElementDefault(const T& value); void SetElementDefault(const T& value);
/**
* Sets the minimum element value of the settings variable.
*
* @param value Minimum element value
*/
void SetElementMinimum(const T& value); void SetElementMinimum(const T& value);
/**
* Sets the maximum element value of the settings variable.
*
* @param value Maximum element value
*/
void SetElementMaximum(const T& value); void SetElementMaximum(const T& value);
/**
* Sets a custom validator function for each element of the settings
* variable.
*
* @param validator Element validator function
*/
void SetElementValidator(std::function<bool(const T&)> validator); void SetElementValidator(std::function<bool(const T&)> validator);
protected:
virtual bool Equals(const SettingsVariableBase& o) const override;
private: private:
class Impl; class Impl;
std::unique_ptr<Impl> p; std::unique_ptr<Impl> p;
}; };
#ifdef SETTINGS_CONTAINER_IMPLEMENTATION #ifdef SETTINGS_CONTAINER_IMPLEMENTATION
template class SettingsContainer<std::vector<int64_t>>; template class SettingsContainer<std::vector<std::int64_t>>;
#endif #endif
} // namespace settings } // namespace settings

View file

@ -3,6 +3,11 @@
#include <scwx/qt/settings/settings_variable.hpp> #include <scwx/qt/settings/settings_variable.hpp>
#include <scwx/util/logger.hpp> #include <scwx/util/logger.hpp>
#include <optional>
#include <boost/json.hpp>
#include <fmt/ostream.h>
namespace scwx namespace scwx
{ {
namespace qt namespace qt
@ -11,16 +16,16 @@ namespace settings
{ {
static const std::string logPrefix_ = "scwx::qt::settings::settings_variable"; static const std::string logPrefix_ = "scwx::qt::settings::settings_variable";
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
template<class T> template<class T>
class SettingsVariable<T>::Impl class SettingsVariable<T>::Impl
{ {
public: public:
explicit Impl(const std::string& name) : name_ {name} {} explicit Impl() {}
~Impl() {} ~Impl() {}
const std::string name_;
T value_ {}; T value_ {};
T default_ {}; T default_ {};
std::optional<T> staged_ {}; std::optional<T> staged_ {};
@ -31,7 +36,7 @@ public:
template<class T> template<class T>
SettingsVariable<T>::SettingsVariable(const std::string& name) : SettingsVariable<T>::SettingsVariable(const std::string& name) :
p(std::make_unique<Impl>(name)) SettingsVariableBase(name), p(std::make_unique<Impl>())
{ {
} }
template<class T> template<class T>
@ -44,9 +49,16 @@ SettingsVariable<T>&
SettingsVariable<T>::operator=(SettingsVariable&&) noexcept = default; SettingsVariable<T>::operator=(SettingsVariable&&) noexcept = default;
template<class T> template<class T>
std::string SettingsVariable<T>::name() const inline auto FormatParameter(const T& value)
{ {
return p->name_; if constexpr (std::is_integral_v<T> || std::is_same_v<T, std::string>)
{
return value;
}
else
{
return fmt::join(value, ", ");
}
} }
template<class T> template<class T>
@ -81,14 +93,26 @@ bool SettingsVariable<T>::SetValueOrDefault(const T& value)
} }
else if (p->minimum_.has_value() && value < p->minimum_) 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_; p->value_ = *p->minimum_;
} }
else if (p->maximum_.has_value() && value > p->maximum_) 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_; p->value_ = *p->maximum_;
} }
else else
{ {
logger_->warn("{} validation failed ({}), setting to default: {}",
name(),
FormatParameter<T>(value),
FormatParameter<T>(p->default_));
p->value_ = p->default_; p->value_ = p->default_;
} }
@ -164,6 +188,59 @@ bool SettingsVariable<T>::Validate(const T& value) const
(p->validator_ == nullptr || p->validator_(value))); // User-validation (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>
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 settings
} // namespace qt } // namespace qt
} // namespace scwx } // namespace scwx

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include <scwx/qt/settings/settings_variable_base.hpp>
#include <functional> #include <functional>
#include <memory>
#include <string>
namespace scwx namespace scwx
{ {
@ -12,7 +12,7 @@ namespace settings
{ {
template<class T> template<class T>
class SettingsVariable class SettingsVariable : public SettingsVariableBase
{ {
public: public:
explicit SettingsVariable(const std::string& name); explicit SettingsVariable(const std::string& name);
@ -24,25 +24,120 @@ public:
SettingsVariable(SettingsVariable&&) noexcept; SettingsVariable(SettingsVariable&&) noexcept;
SettingsVariable& operator=(SettingsVariable&&) noexcept; SettingsVariable& operator=(SettingsVariable&&) noexcept;
std::string name() const; /**
* Gets the current value of the settings variable.
*
* @return Current value
*/
T GetValue() const;
T GetValue() const; /**
bool SetValue(const T& value); * Sets the current value of the settings variable.
*
* @param value Value to set
*
* @return true if the current value was set, false if the value failed
* validation.
*/
bool SetValue(const T& value);
/**
* Sets the current value of the settings variable. If the value is out of
* range, the value is set to the minimum or maximum. If the value fails
* validation, the value is set to default.
*
* @param value Value to set
*
* @return true if the value is valid, false if the value was modified.
*/
virtual bool SetValueOrDefault(const T& value); virtual bool SetValueOrDefault(const T& value);
void SetValueToDefault();
/**
* Sets the current value of the settings variable to default.
*/
void SetValueToDefault() override;
/**
* Sets the staged value of the settings variable.
*
* @param value Value to stage
*
* @return true if the staged value was set, false if the value failed
* validation.
*/
bool StageValue(const T& value); bool StageValue(const T& value);
/**
* Sets the current value of the settings variable to the staged value.
*/
void Commit(); void Commit();
/**
* Validate the value against the defined parameters of the settings
* variable.
*
* @param value Value to validate
*
* @return true if the value is valid, false if the value failed validation.
*/
virtual bool Validate(const T& value) const; virtual bool Validate(const T& value) const;
/**
* Gets the default value of the settings variable.
*
* @return Default value
*/
T GetDefault() const; T GetDefault() const;
/**
* Sets the default value of the settings variable.
*
* @param value Default value
*/
void SetDefault(const T& value); void SetDefault(const T& value);
/**
* Sets the minimum value of the settings variable.
*
* @param value Minimum value
*/
void SetMinimum(const T& value); void SetMinimum(const T& value);
/**
* Sets the maximum value of the settings variable.
*
* @param value Maximum value
*/
void SetMaximum(const T& value); void SetMaximum(const T& value);
/**
* Sets a custom validator function for the settings variable.
*
* @param validator Validator function
*/
void SetValidator(std::function<bool(const T&)> validator); void SetValidator(std::function<bool(const T&)> validator);
/**
* Reads the value from the JSON object. If the read value is out of range,
* the value is set to the minimum or maximum. If the read value fails
* validation, the value is set to default.
*
* @param json JSON object to read
*
* @return true if the read value is valid, false if the value was modified.
*/
virtual bool ReadValue(const boost::json::object& json) override;
/**
* Writes the current value to the JSON object.
*
* @param json JSON object to write
*/
virtual void WriteValue(boost::json::object& json) const override;
protected:
virtual bool Equals(const SettingsVariableBase& o) const override;
private: private:
class Impl; class Impl;
std::unique_ptr<Impl> p; std::unique_ptr<Impl> p;
@ -50,11 +145,11 @@ private:
#ifdef SETTINGS_VARIABLE_IMPLEMENTATION #ifdef SETTINGS_VARIABLE_IMPLEMENTATION
template class SettingsVariable<bool>; template class SettingsVariable<bool>;
template class SettingsVariable<int64_t>; template class SettingsVariable<std::int64_t>;
template class SettingsVariable<std::string>; template class SettingsVariable<std::string>;
// Containers are not to be used directly // Containers are not to be used directly
template class SettingsVariable<std::vector<int64_t>>; template class SettingsVariable<std::vector<std::int64_t>>;
#endif #endif
} // namespace settings } // namespace settings

View file

@ -0,0 +1,54 @@
#include <scwx/qt/settings/settings_variable_base.hpp>
namespace scwx
{
namespace qt
{
namespace settings
{
static const std::string logPrefix_ =
"scwx::qt::settings::settings_variable_base";
class SettingsVariableBase::Impl
{
public:
explicit Impl(const std::string& name) : name_ {name} {}
~Impl() {}
const std::string name_;
};
SettingsVariableBase::SettingsVariableBase(const std::string& name) :
p(std::make_unique<Impl>(name))
{
}
SettingsVariableBase::~SettingsVariableBase() = default;
SettingsVariableBase::SettingsVariableBase(SettingsVariableBase&&) noexcept =
default;
SettingsVariableBase&
SettingsVariableBase::operator=(SettingsVariableBase&&) noexcept = default;
std::string SettingsVariableBase::name() const
{
return p->name_;
}
bool SettingsVariableBase::Equals(const SettingsVariableBase& o) const
{
return p->name_ == o.p->name_;
}
bool operator==(const SettingsVariableBase& lhs,
const SettingsVariableBase& rhs)
{
return typeid(lhs) == typeid(rhs) && lhs.Equals(rhs);
}
} // namespace settings
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,76 @@
#pragma once
#include <memory>
#include <string>
#include <boost/json/object.hpp>
namespace scwx
{
namespace qt
{
namespace settings
{
/**
* @brief Settings Variable base class
*/
class SettingsVariableBase
{
protected:
explicit SettingsVariableBase(const std::string& name);
~SettingsVariableBase();
public:
SettingsVariableBase(const SettingsVariableBase&) = delete;
SettingsVariableBase& operator=(const SettingsVariableBase&) = delete;
SettingsVariableBase(SettingsVariableBase&&) noexcept;
SettingsVariableBase& operator=(SettingsVariableBase&&) noexcept;
std::string name() const;
/**
* Sets the current value of the settings variable to default.
*/
virtual void SetValueToDefault() = 0;
/**
* Sets the current value of the settings variable to the staged value.
*/
virtual void Commit() = 0;
/**
* Reads the value from the JSON object. If the read value is out of range,
* the value is set to the minimum or maximum. If the read value fails
* validation, the value is set to default.
*
* @param json JSON object to read
*
* @return true if the read value is valid, false if the value was modified.
*/
virtual bool ReadValue(const boost::json::object& json) = 0;
/**
* Writes the current value to the JSON object.
*
* @param json JSON object to write
*/
virtual void WriteValue(boost::json::object& json) const = 0;
protected:
friend bool operator==(const SettingsVariableBase& lhs,
const SettingsVariableBase& rhs);
virtual bool Equals(const SettingsVariableBase& o) const;
private:
class Impl;
std::unique_ptr<Impl> p;
};
bool operator==(const SettingsVariableBase& lhs,
const SettingsVariableBase& rhs);
} // namespace settings
} // namespace qt
} // namespace scwx