#include "converter.h" // A FieldInterface provides a simple interface for converting a field to/from JSON. // It's constructed with a pointer to some data, and has two functions: // - 'get' returns the pointed-to data, converted to JSON // - 'set' assigns the pointed-to data to a given QJsonValue, with appropriate conversion. // Returns any errors that occur during conversion/assignment. // // A FieldInterface is normally constructed using the 'makeFieldInterface' function: // Example: // int someField = 0; // FieldInterface* fi = makeFieldInterface(&someField); // fi->set(QJsonValue("5")); // someField is now 5 // // There are additional implementations of 'makeFieldInterface' that let you specify what valid values are. // Example: // int someField = 0; // int min = 1, max = 4; // FieldInterface* fi = makeFieldInterface(&someField, min, max); // fi->set(QJsonValue("5")); // someField is now 4 (defaults to closest bound), error messages returned // or // QString someField = ""; // QList options = {"hello","hi there"}; // FieldInterface* fi = makeFieldInterface(&someField, options); // fi->set(QJsonValue("5")); // someField is now "hello" (defaults to first element), error messages returned // // If a type or value is given that cannot be converted to the destination type, the field remains unchanged. // int someField = 0; // int min = 1, max = 4; // FieldInterface* fi = makeFieldInterface(&someField, min, max); // fi->set(QJsonValue()); // Cannot convert type. someField is still 0, error messages returned // fi->set(QJsonValue("hi there")); // Cannot convert value. someField is still 0, error messages returned // Base class lets us use the interface without any type information. class FieldInterface { public: FieldInterface(){}; virtual ~FieldInterface() {}; virtual QJsonValue get() const = 0; virtual QStringList set(const QJsonValue& json) const = 0; }; template class BasicFieldInterface : public FieldInterface { public: BasicFieldInterface(T* field) : m_field(field) { Q_ASSERT(m_field); }; virtual ~BasicFieldInterface() {}; virtual QJsonValue get() const override {return Converter::toJson(*m_field);} virtual QStringList set(const QJsonValue& json) const override { QStringList errors; auto value = Converter::fromJson(json, &errors); if (errors.isEmpty()) *m_field = value; // Don't bother changing the value if conversion failed return errors; } protected: T* m_field; }; template static FieldInterface* makeFieldInterface(T* field) { return new BasicFieldInterface(field); } // Create a regular FieldInterface, but override 'set' to use the given min/max. template static FieldInterface* makeFieldInterface(T* field, const T& min, const T& max) { class BoundedFieldInterface : public BasicFieldInterface { public: BoundedFieldInterface(T* field, const T& min, const T& max) : BasicFieldInterface(field), m_min(min), m_max(max) {}; virtual ~BoundedFieldInterface() {}; virtual QStringList set(const QJsonValue& json) const override { QStringList errors; auto value = Converter::fromJson(json, &errors); if (errors.isEmpty()) { // Don't bother changing the value if conversion failed value = Converter::clamp(value, m_min, m_max, &errors); *this->m_field = value; } return errors; } private: const T m_min; const T m_max; }; return new BoundedFieldInterface(field, min, max); } // Create a regular FieldInterface, but override 'set' to use the given 'acceptableValues'. template static FieldInterface* makeFieldInterface(T* field, const QList& acceptableValues) { Q_ASSERT(!acceptableValues.isEmpty()); class BoundedFieldInterface : public BasicFieldInterface { public: BoundedFieldInterface(T* field, const QList& acceptableValues) : BasicFieldInterface(field), m_acceptableValues(acceptableValues.begin(), acceptableValues.end()), m_defaultValue(acceptableValues.first()) {}; virtual ~BoundedFieldInterface() {}; virtual QStringList set(const QJsonValue& json) const override { QStringList errors; auto value = Converter::fromJson(json, &errors); if (errors.isEmpty()) { if (!m_acceptableValues.contains(value)) { value = m_defaultValue; errors.append("Invalid value."); } *this->m_field = value; } return errors; } private: // The order of the list only matters for determining the default value, // so save that separately and convert the list to a set for better lookup speed. const QSet m_acceptableValues; const T m_defaultValue; }; return new BoundedFieldInterface(field, acceptableValues); } ///////////////////////////////////////////////////////////////////////////////////////////////////////// // FieldManager manages a QHash mapping string keys to FieldInterfaces. // This makes it easy to map many fields to/from JSON without explicitly serializing anything. class FieldManager { public: ~FieldManager(){ clear(); } QStringList setField(const QString& key, const QJsonValue& value) const { auto it = m_fields.find(key); return (it != m_fields.end()) ? it.value()->set(value) : QStringList(); } QJsonValue getField(const QString& key) const { auto it = m_fields.find(key); return (it != m_fields.end()) ? it.value()->get() : QJsonValue(); } QJsonObject getFields() const { QJsonObject obj; for (auto it = m_fields.constBegin(); it != m_fields.constEnd(); it++) { obj[it.key()] = it.value()->get(); } return obj; } void clear() { qDeleteAll(m_fields); m_fields.clear(); } bool hasField(const QString& key) const {return m_fields.contains(key);} template void addField(T* field, const QString& key) { Q_ASSERT(!m_fields.contains(key)); m_fields.insert(key, makeFieldInterface(field)); } template void addField(T* field, const QString& key, const T& min, const T& max) { Q_ASSERT(!m_fields.contains(key)); m_fields.insert(key, makeFieldInterface(field, min, max)); } template void addField(T* field, const QString& key, const QList& acceptableValues) { Q_ASSERT(!m_fields.contains(key)); m_fields.insert(key, makeFieldInterface(field, acceptableValues)); } private: QHash m_fields; };