io/fileview: add adapter support and JsonAdapter
This commit is contained in:
parent
cb69c2d016
commit
fee4942771
6 changed files with 487 additions and 2 deletions
|
@ -2,6 +2,7 @@ qt_add_library(quickshell-io STATIC
|
|||
datastream.cpp
|
||||
process.cpp
|
||||
fileview.cpp
|
||||
jsonadapter.cpp
|
||||
ipccomm.cpp
|
||||
ipc.cpp
|
||||
ipchandler.cpp
|
||||
|
|
|
@ -289,6 +289,12 @@ void FileViewWriter::write(
|
|||
}
|
||||
}
|
||||
|
||||
FileView::~FileView() {
|
||||
if (this->mAdapter) {
|
||||
this->mAdapter->setFileView(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void FileView::loadAsync(bool doStringConversion) {
|
||||
// Writes update via operationFinished, making a read both invalid and outdated.
|
||||
if (!this->liveOperation || this->pathInFlight != this->targetPath) {
|
||||
|
@ -636,4 +642,55 @@ void FileView::setBlockAllReads(bool blockAllReads) {
|
|||
}
|
||||
}
|
||||
|
||||
FileViewAdapter* FileView::adapter() const { return this->mAdapter; }
|
||||
|
||||
void FileView::setAdapter(FileViewAdapter* adapter) {
|
||||
if (adapter == this->mAdapter) return;
|
||||
|
||||
if (this->mAdapter) {
|
||||
this->mAdapter->setFileView(nullptr);
|
||||
QObject::disconnect(this->mAdapter, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mAdapter = adapter;
|
||||
|
||||
if (adapter) {
|
||||
this->mAdapter->setFileView(this);
|
||||
QObject::connect(adapter, &FileViewAdapter::adapterUpdated, this, &FileView::adapterUpdated);
|
||||
QObject::connect(adapter, &QObject::destroyed, this, &FileView::onAdapterDestroyed);
|
||||
}
|
||||
|
||||
emit this->adapterChanged();
|
||||
}
|
||||
|
||||
void FileView::writeAdapter() {
|
||||
if (!this->mAdapter) {
|
||||
qmlWarning(this) << "Cannot call writeAdapter without an adapter.";
|
||||
return;
|
||||
}
|
||||
|
||||
this->setData(this->mAdapter->serializeAdapter());
|
||||
}
|
||||
|
||||
void FileView::onAdapterDestroyed() { this->mAdapter = nullptr; }
|
||||
|
||||
void FileViewAdapter::setFileView(FileView* fileView) {
|
||||
if (fileView == this->mFileView) return;
|
||||
|
||||
if (this->mFileView) {
|
||||
QObject::disconnect(this->mFileView, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mFileView = fileView;
|
||||
|
||||
if (fileView) {
|
||||
QObject::connect(fileView, &FileView::dataChanged, this, &FileViewAdapter::onDataChanged);
|
||||
this->setFileView(fileView);
|
||||
} else {
|
||||
this->setFileView(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void FileViewAdapter::onDataChanged() { this->deserializeAdapter(this->mFileView->data()); }
|
||||
|
||||
} // namespace qs::io
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <qqmlparserstatus.h>
|
||||
#include <qrunnable.h>
|
||||
#include <qstringview.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../core/doc.hpp"
|
||||
|
@ -140,11 +141,13 @@ public:
|
|||
bool doAtomicWrite;
|
||||
};
|
||||
|
||||
///! Simplified reader for small files.
|
||||
class FileViewAdapter;
|
||||
|
||||
///! Simple accessor for small files.
|
||||
/// A reader for small to medium files that don't need seeking/cursor access,
|
||||
/// suitable for most text files.
|
||||
///
|
||||
/// #### Example: Reading a JSON
|
||||
/// #### Example: Reading a JSON as text
|
||||
/// ```qml
|
||||
/// FileView {
|
||||
/// id: jsonFile
|
||||
|
@ -156,6 +159,8 @@ public:
|
|||
///
|
||||
/// readonly property var jsonData: JSON.parse(jsonFile.text())
|
||||
/// ```
|
||||
///
|
||||
/// Also see @@JsonAdapter for an alternative way to handle reading and writing JSON files.
|
||||
class FileView: public QObject {
|
||||
Q_OBJECT;
|
||||
// clang-format off
|
||||
|
@ -215,6 +220,14 @@ class FileView: public QObject {
|
|||
/// > }
|
||||
/// > ```
|
||||
Q_PROPERTY(bool watchChanges READ default WRITE default NOTIFY watchChangesChanged BINDABLE bindableWatchChanges);
|
||||
/// In addition to directly reading/writing the file as text, *adapters* can be used to
|
||||
/// expose a file's content in new ways.
|
||||
///
|
||||
/// An adapter will automatically be given the loaded file's content.
|
||||
/// Its state may be saved with @@writeAdapter().
|
||||
///
|
||||
/// Currently the only adapter is @@JsonAdapter.
|
||||
Q_PROPERTY(FileViewAdapter* adapter READ adapter WRITE setAdapter NOTIFY adapterChanged);
|
||||
|
||||
QSDOC_HIDE Q_PROPERTY(QString __path READ path WRITE setPath NOTIFY pathChanged);
|
||||
QSDOC_HIDE Q_PROPERTY(QString __text READ text NOTIFY internalTextChanged);
|
||||
|
@ -230,11 +243,14 @@ class FileView: public QObject {
|
|||
QSDOC_HIDE Q_PROPERTY(bool __blockAllReads READ blockAllReads WRITE setBlockAllReads NOTIFY blockAllReadsChanged);
|
||||
QSDOC_HIDE Q_PROPERTY(bool __printErrors READ default WRITE default NOTIFY printErrorsChanged BINDABLE bindablePrintErrors);
|
||||
// clang-format on
|
||||
Q_CLASSINFO("DefaultProperty", "adapter");
|
||||
QML_NAMED_ELEMENT(FileViewInternal);
|
||||
QSDOC_NAMED_ELEMENT(FileView);
|
||||
|
||||
public:
|
||||
explicit FileView(QObject* parent = nullptr): QObject(parent) {}
|
||||
~FileView() override;
|
||||
Q_DISABLE_COPY_MOVE(FileView);
|
||||
|
||||
/// Returns the data of the file specified by @@path as text.
|
||||
///
|
||||
|
@ -280,6 +296,8 @@ public:
|
|||
/// This will not block if @@blockLoading is set, only if @@blockAllReads is true.
|
||||
/// It acts the same as changing @@path to a new file, except loading the same file.
|
||||
Q_INVOKABLE void reload();
|
||||
/// Write the content of the current @@adapter to the selected file.
|
||||
Q_INVOKABLE void writeAdapter();
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(const QString& path);
|
||||
|
@ -312,6 +330,9 @@ public:
|
|||
[[nodiscard]] QBindable<bool> bindablePrintErrors() { return &this->bPrintErrors; }
|
||||
[[nodiscard]] QBindable<bool> bindableWatchChanges() { return &this->bWatchChanges; }
|
||||
|
||||
[[nodiscard]] FileViewAdapter* adapter() const;
|
||||
void setAdapter(FileViewAdapter* adapter);
|
||||
|
||||
signals:
|
||||
/// Emitted if the file was loaded successfully.
|
||||
void loaded();
|
||||
|
@ -323,6 +344,8 @@ signals:
|
|||
void saveFailed(qs::io::FileViewError::Enum error);
|
||||
/// Emitted if the file changes on disk and @@watchChanges is true.
|
||||
void fileChanged();
|
||||
/// Emitted when the active @@adapter$'s data is changed.
|
||||
void adapterUpdated();
|
||||
|
||||
void pathChanged();
|
||||
QSDOC_HIDE void internalTextChanged();
|
||||
|
@ -337,9 +360,11 @@ signals:
|
|||
void atomicWritesChanged();
|
||||
void printErrorsChanged();
|
||||
void watchChangesChanged();
|
||||
void adapterChanged();
|
||||
|
||||
private slots:
|
||||
void operationFinished();
|
||||
void onAdapterDestroyed();
|
||||
|
||||
private:
|
||||
void loadAsync(bool doStringConversion);
|
||||
|
@ -373,6 +398,7 @@ private:
|
|||
bool mBlockLoading = false;
|
||||
bool mBlockAllReads = false;
|
||||
|
||||
FileViewAdapter* mAdapter = nullptr;
|
||||
QFileSystemWatcher* watcher = nullptr;
|
||||
|
||||
GuardedEmitter<&FileView::internalTextChanged> textChangedEmitter;
|
||||
|
@ -406,4 +432,26 @@ public:
|
|||
void setBlockAllReads(bool blockAllReads);
|
||||
};
|
||||
|
||||
/// See @@FileView.adapter.
|
||||
class FileViewAdapter: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("");
|
||||
|
||||
public:
|
||||
void setFileView(FileView* fileView);
|
||||
virtual void deserializeAdapter(const QByteArray& data) = 0;
|
||||
[[nodiscard]] virtual QByteArray serializeAdapter() = 0;
|
||||
|
||||
signals:
|
||||
/// This signal is fired when data in the adapter changes, and triggers @@FileView.adapterUpdated(s).
|
||||
void adapterUpdated();
|
||||
|
||||
private slots:
|
||||
void onDataChanged();
|
||||
|
||||
protected:
|
||||
FileView* mFileView = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::io
|
||||
|
|
262
src/io/jsonadapter.cpp
Normal file
262
src/io/jsonadapter.cpp
Normal file
|
@ -0,0 +1,262 @@
|
|||
#include "jsonadapter.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qjsonvalue.h>
|
||||
#include <qjsvalue.h>
|
||||
#include <qmetaobject.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qqml.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmlinfo.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qstringview.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
namespace qs::io {
|
||||
|
||||
void JsonAdapter::componentComplete() { this->connectNotifiers(); }
|
||||
|
||||
void JsonAdapter::deserializeAdapter(const QByteArray& data) {
|
||||
if (data.isEmpty()) return;
|
||||
|
||||
// Importing this makes CI builds fail for some reason.
|
||||
QJsonParseError error; // NOLINT (misc-include-cleaner)
|
||||
auto json = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qmlWarning(this) << "Failed to deserialize json: " << error.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json.isObject()) {
|
||||
qmlWarning(this) << "Failed to deserialize json: not an object";
|
||||
return;
|
||||
}
|
||||
|
||||
this->changesBlocked = true;
|
||||
this->oldCreatedObjects = this->createdObjects;
|
||||
this->createdObjects.clear();
|
||||
|
||||
this->deserializeRec(json.object(), this, &JsonAdapter::staticMetaObject);
|
||||
|
||||
for (auto* object: oldCreatedObjects) {
|
||||
delete object; // FIXME: QMetaType::destroy?
|
||||
}
|
||||
|
||||
this->oldCreatedObjects.clear();
|
||||
this->changesBlocked = false;
|
||||
|
||||
this->connectNotifiers();
|
||||
}
|
||||
|
||||
void JsonAdapter::connectNotifiers() {
|
||||
auto notifySlot = JsonAdapter::staticMetaObject.indexOfSlot("onPropertyChanged()");
|
||||
connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject);
|
||||
}
|
||||
|
||||
void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaObject* base) {
|
||||
const auto* metaObject = obj->metaObject();
|
||||
|
||||
for (auto i = base->propertyOffset(); i != metaObject->propertyCount(); i++) {
|
||||
const auto prop = metaObject->property(i);
|
||||
|
||||
if (prop.isReadable() && prop.hasNotifySignal()) {
|
||||
QMetaObject::connect(obj, prop.notifySignalIndex(), this, notifySlot, Qt::UniqueConnection);
|
||||
|
||||
auto val = prop.read(obj);
|
||||
if (val.canView<JsonObject*>()) {
|
||||
auto* pobj = prop.read(obj).view<JsonObject*>();
|
||||
if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
||||
} else if (val.canConvert<QQmlListProperty<JsonObject>>()) {
|
||||
auto listVal = val.value<QQmlListProperty<JsonObject>>();
|
||||
|
||||
auto len = listVal.count(&listVal);
|
||||
for (auto i = 0; i != len; i++) {
|
||||
auto* pobj = listVal.at(&listVal, i);
|
||||
|
||||
if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JsonAdapter::onPropertyChanged() {
|
||||
if (this->changesBlocked) return;
|
||||
|
||||
this->connectNotifiers();
|
||||
this->adapterUpdated();
|
||||
}
|
||||
|
||||
QByteArray JsonAdapter::serializeAdapter() {
|
||||
return QJsonDocument(this->serializeRec(this, &JsonAdapter::staticMetaObject))
|
||||
.toJson(QJsonDocument::Indented);
|
||||
}
|
||||
|
||||
QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* base) const {
|
||||
QJsonObject json;
|
||||
const auto* metaObject = obj->metaObject();
|
||||
|
||||
for (auto i = base->propertyOffset(); i != metaObject->propertyCount(); i++) {
|
||||
const auto prop = metaObject->property(i);
|
||||
|
||||
if (prop.isReadable() && prop.hasNotifySignal()) {
|
||||
auto val = prop.read(obj);
|
||||
if (val.canView<JsonObject*>()) {
|
||||
auto* pobj = val.view<JsonObject*>();
|
||||
|
||||
if (pobj) {
|
||||
json.insert(prop.name(), serializeRec(pobj, &JsonObject::staticMetaObject));
|
||||
} else {
|
||||
json.insert(prop.name(), QJsonValue::Null);
|
||||
}
|
||||
} else if (val.canConvert<QQmlListProperty<JsonObject>>()) {
|
||||
QJsonArray array;
|
||||
auto listVal = val.value<QQmlListProperty<JsonObject>>();
|
||||
|
||||
auto len = listVal.count(&listVal);
|
||||
for (auto i = 0; i != len; i++) {
|
||||
auto* pobj = listVal.at(&listVal, i);
|
||||
|
||||
if (pobj) {
|
||||
array.push_back(serializeRec(pobj, &JsonObject::staticMetaObject));
|
||||
} else {
|
||||
array.push_back(QJsonValue::Null);
|
||||
}
|
||||
}
|
||||
|
||||
json.insert(prop.name(), array);
|
||||
} else if (val.canConvert<QJSValue>()) {
|
||||
auto variant = val.value<QJSValue>().toVariant();
|
||||
auto jv = QJsonValue::fromVariant(variant);
|
||||
json.insert(prop.name(), jv);
|
||||
} else {
|
||||
auto jv = QJsonValue::fromVariant(val);
|
||||
json.insert(prop.name(), jv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QMetaObject* base) {
|
||||
const auto* metaObject = obj->metaObject();
|
||||
|
||||
for (auto i = base->propertyOffset(); i != metaObject->propertyCount(); i++) {
|
||||
const auto prop = metaObject->property(i);
|
||||
if (json.contains(prop.name())) {
|
||||
auto jval = json.value(prop.name());
|
||||
|
||||
if (prop.metaType() == QMetaType::fromType<QVariant>()) {
|
||||
auto variant = jval.toVariant();
|
||||
auto oldValue = prop.read(this).value<QJSValue>();
|
||||
|
||||
// Calling prop.write with a new QJSValue will cause a property update
|
||||
// even if content is identical.
|
||||
if (jval.toVariant() != oldValue.toVariant()) {
|
||||
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(jval.toVariant());
|
||||
prop.write(this, QVariant::fromValue(jsValue));
|
||||
}
|
||||
} else if (QMetaType::canView(prop.metaType(), QMetaType::fromType<JsonObject*>())) {
|
||||
// FIXME: This doesn't support creating descendants of JsonObject, as QMetaType.metaObject()
|
||||
// returns null for QML types.
|
||||
|
||||
if (jval.isObject()) {
|
||||
auto* currentValue = prop.read(obj).view<JsonObject*>();
|
||||
auto isNew = currentValue == nullptr;
|
||||
|
||||
if (isNew) {
|
||||
// metaObject->metaType removes the pointer
|
||||
currentValue =
|
||||
static_cast<JsonObject*>(prop.metaType().metaObject()->metaType().create());
|
||||
|
||||
currentValue->setParent(this);
|
||||
this->createdObjects.push_back(currentValue);
|
||||
} else if (oldCreatedObjects.removeOne(currentValue)) {
|
||||
createdObjects.push_back(currentValue);
|
||||
}
|
||||
|
||||
this->deserializeRec(jval.toObject(), currentValue, &JsonObject::staticMetaObject);
|
||||
|
||||
if (isNew) prop.write(obj, QVariant::fromValue(currentValue));
|
||||
} else if (jval.isNull()) {
|
||||
prop.write(obj, QVariant::fromValue(nullptr));
|
||||
} else {
|
||||
qmlWarning(this) << "Failed to deserialize property " << prop.name() << " as object. Got "
|
||||
<< jval.toVariant().typeName();
|
||||
}
|
||||
} else if (QMetaType::canConvert(
|
||||
prop.metaType(),
|
||||
QMetaType::fromType<QQmlListProperty<JsonObject>>()
|
||||
))
|
||||
{
|
||||
auto pval = prop.read(this);
|
||||
|
||||
if (pval.canConvert<QQmlListProperty<JsonObject>>()) {
|
||||
auto lp = pval.value<QQmlListProperty<JsonObject>>();
|
||||
auto array = jval.toArray();
|
||||
auto lpCount = lp.count(&lp);
|
||||
|
||||
auto i = 0;
|
||||
for (; i != array.count(); i++) {
|
||||
JsonObject* currentValue = nullptr;
|
||||
auto isNew = i >= lpCount;
|
||||
|
||||
const auto& jsonValue = array.at(i);
|
||||
if (jsonValue.isObject()) {
|
||||
if (isNew) {
|
||||
currentValue = lp.at(&lp, i);
|
||||
if (oldCreatedObjects.removeOne(currentValue)) {
|
||||
createdObjects.push_back(currentValue);
|
||||
}
|
||||
} else {
|
||||
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
|
||||
currentValue = static_cast<JsonObject*>(QMetaType::fromType<JsonObject>().create());
|
||||
currentValue->setParent(this);
|
||||
this->createdObjects.push_back(currentValue);
|
||||
}
|
||||
|
||||
this->deserializeRec(
|
||||
jsonValue.toObject(),
|
||||
currentValue,
|
||||
&JsonObject::staticMetaObject
|
||||
);
|
||||
} else if (!jsonValue.isNull()) {
|
||||
qmlWarning(this) << "Failed to deserialize property" << prop.name()
|
||||
<< ": Member of object array is not an object: "
|
||||
<< jsonValue.toVariant().typeName();
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
lp.append(&lp, currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < lpCount; i++) {
|
||||
lp.removeLast(&lp);
|
||||
}
|
||||
} else {
|
||||
qmlWarning(this) << "Failed to deserialize property " << prop.name()
|
||||
<< ": property is a list<JsonObject> but contains null.";
|
||||
}
|
||||
} else {
|
||||
auto variant = jval.toVariant();
|
||||
|
||||
if (variant.convert(prop.metaType())) {
|
||||
prop.write(obj, variant);
|
||||
} else {
|
||||
qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected "
|
||||
<< prop.metaType().name() << " but got " << jval.toVariant().typeName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace qs::io
|
116
src/io/jsonadapter.hpp
Normal file
116
src/io/jsonadapter.hpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qjsonvalue.h>
|
||||
#include <qjsvalue.h>
|
||||
#include <qlist.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qstringview.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "fileview.hpp"
|
||||
|
||||
namespace qs::io {
|
||||
|
||||
/// See @@JsonAdapter.
|
||||
class JsonObject: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
};
|
||||
|
||||
///! FileView adapter for accessing JSON files.
|
||||
/// JsonAdapter is a @@FileView adapter that exposes a JSON file as a set of QML
|
||||
/// properties that can be read and written to.
|
||||
///
|
||||
/// Each property defined in a JsonAdapter corresponds to a key in the JSON file.
|
||||
/// Supported property types are:
|
||||
/// - Primitves (`int`, `bool`, `string`, `real`)
|
||||
/// - Sub-object adapters (@@JsonObject$)
|
||||
/// - JSON objects and arrays, as a `var` type
|
||||
/// - Lists of any of the above (`list<string>` etc)
|
||||
///
|
||||
/// When the @@FileView$'s data is loaded, properties of a JsonAdapter or
|
||||
/// sub-object adapter (@@JsonObject$) are updated if their values have changed.
|
||||
///
|
||||
/// When properties of a JsonAdapter or sub-object adapter are changed from QML,
|
||||
/// @@FileView.adapterUpdated(s) is emitted, which may be used to save the file's new
|
||||
/// state (see @@FileView.writeAdapter()$).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```qml
|
||||
/// @@FileView {
|
||||
/// path: "/path/to/file"
|
||||
///
|
||||
/// // when changes are made on disk, reload the file's content
|
||||
/// watchChanges: true
|
||||
/// onFileChanged: reload()
|
||||
///
|
||||
/// // when changes are made to properties in the adapter, save them
|
||||
/// onAdapterUpdated: writeAdapter()
|
||||
///
|
||||
/// JsonAdapter {
|
||||
/// property string myStringProperty: "default value"
|
||||
/// onMyStringPropertyChanged: {
|
||||
/// console.log("myStringProperty was changed via qml or on disk")
|
||||
/// }
|
||||
///
|
||||
/// property list<string> stringList: [ "default", "value" ]
|
||||
///
|
||||
/// property JsonObject subObject: JsonObject {
|
||||
/// property string subObjectProperty: "default value"
|
||||
/// onSubObjectPropertyChanged: console.log("same as above")
|
||||
/// }
|
||||
///
|
||||
/// // works the same way as subObject
|
||||
/// property var inlineJson: { "a": "b" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The above snippet produces the JSON document below:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "myStringProperty": "default value",
|
||||
/// "stringList": [
|
||||
/// "default",
|
||||
/// "value"
|
||||
/// ],
|
||||
/// "subObject": {
|
||||
/// "subObjectProperty": "default value"
|
||||
/// },
|
||||
/// "inlineJson": {
|
||||
/// "a": "b"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
class JsonAdapter
|
||||
: public FileViewAdapter
|
||||
, public QQmlParserStatus {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
void classBegin() override {}
|
||||
void componentComplete() override;
|
||||
|
||||
void deserializeAdapter(const QByteArray& data) override;
|
||||
[[nodiscard]] QByteArray serializeAdapter() override;
|
||||
|
||||
private slots:
|
||||
void onPropertyChanged();
|
||||
|
||||
private:
|
||||
void connectNotifiers();
|
||||
void connectNotifiersRec(int notifySlot, QObject* obj, const QMetaObject* base);
|
||||
void deserializeRec(const QJsonObject& json, QObject* obj, const QMetaObject* base);
|
||||
[[nodiscard]] QJsonObject serializeRec(const QObject* obj, const QMetaObject* base) const;
|
||||
|
||||
bool changesBlocked = false;
|
||||
QList<JsonObject*> createdObjects;
|
||||
QList<JsonObject*> oldCreatedObjects;
|
||||
};
|
||||
|
||||
} // namespace qs::io
|
|
@ -5,6 +5,7 @@ headers = [
|
|||
"socket.hpp",
|
||||
"process.hpp",
|
||||
"fileview.hpp",
|
||||
"jsonadapter.hpp",
|
||||
"ipchandler.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue