Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 178 additions & 30 deletions src/private/dconfigwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

#include <QDebug>
#include <QCoreApplication>
#include <QLoggingCategory>

Check warning on line 9 in src/private/dconfigwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QLoggingCategory> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QQmlEngine>

Check warning on line 10 in src/private/dconfigwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QQmlEngine> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QTimer>

Check warning on line 11 in src/private/dconfigwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QTimer> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QQmlInfo>

Check warning on line 12 in src/private/dconfigwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QQmlInfo> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <private/qqmlopenmetaobject_p.h>

Check warning on line 13 in src/private/dconfigwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <private/qqmlopenmetaobject_p.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <DConfig>

Check warning on line 15 in src/private/dconfigwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <DConfig> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#ifndef QT_DEBUG
Q_LOGGING_CATEGORY(cfLog, "dtk.dsg.config" , QtInfoMsg);
Expand All @@ -22,8 +23,7 @@
DCORE_USE_NAMESPACE;

// the properties and previous values.
using DefalutProperties = QMap<QByteArray, QVariant>;
static DefalutProperties propertyAndValues(const QObject* obj)
static DConfigWrapper::DefalutProperties propertyAndValues(const QObject* obj)
{
QMap<QByteArray, QVariant> properties;
const QMetaObject *mo = obj->metaObject();
Expand Down Expand Up @@ -138,12 +138,53 @@
m_subpath = subpath;
}

QObject *DConfigWrapper::proxyTarget() const
{
return m_proxyTarget;
}

void DConfigWrapper::setProxyTarget(QObject *newProxyTarget)
{
if (m_proxyTarget == newProxyTarget)
return;

if (m_proxyTargetValueChangedConnection) {
disconnect(m_proxyTargetValueChangedConnection);
}

m_proxyTarget = newProxyTarget;

if (m_componentCompleted) {
if (m_proxyTarget)
initWithProxyTarget();
else
initWithDConfig();
}

Q_EMIT proxyTargetChanged();
}

/*!
* \brief \sa DConfig keyList()
* \return
*/
QStringList DConfigWrapper::keyList() const
{
if (m_proxyTarget) {
const QMetaObject *mo = m_proxyTarget->metaObject();
const int offset = mo->propertyOffset();
const int count = mo->propertyCount();

QStringList keyList = {};
keyList.reserve(count);

for (int i = offset; i < count; ++i) {
const QMetaProperty &property = mo->property(i);
keyList << property.name();
}
return keyList;
}

if (!impl)
return QStringList();

Expand All @@ -156,6 +197,18 @@
*/
bool DConfigWrapper::isValid() const
{
if (m_proxyTarget) {
int index = m_proxyTarget->metaObject()->indexOfMethod("isInitializeSucceed()");
if (index < 0) {
qmlEngine(this)->throwError(QString("Can't know whether the DConfig is valid, "
"because the proxyTarget doesn't have "
"isInitializeSucceed method"));
return false;
}

return m_proxyTarget->metaObject()->method(index).invoke(m_proxyTarget);
}

if (!impl)
return false;

Expand All @@ -168,6 +221,13 @@
*/
QVariant DConfigWrapper::value(const QString &key, const QVariant &fallback) const
{
if (m_proxyTarget) {
if (isDefaultValue(key))
return fallback;

return m_proxyTarget->property(key.toUtf8().constData());
}

if (!impl)
return fallback;

Expand All @@ -180,6 +240,11 @@
*/
void DConfigWrapper::setValue(const QString &key, const QVariant &value)
{
if (m_proxyTarget) {
m_proxyTarget->setProperty(key.toUtf8().constData(), value);
return;
}

if (!impl)
return;

Expand All @@ -188,6 +253,13 @@

void DConfigWrapper::resetValue(const QString &key)
{
if (m_proxyTarget) {
int index = m_proxyTarget->metaObject()->indexOfProperty(key.toUtf8().constData());
if (index >= 0)
m_proxyTarget->metaObject()->property(index).reset(m_proxyTarget);
return;
}

if (!impl)
return;

Expand All @@ -196,7 +268,7 @@

void DConfigWrapper::classBegin()
{

m_componentCompleted = false;
}

/*!
Expand All @@ -207,51 +279,127 @@
*/
void DConfigWrapper::componentComplete()
{
m_componentCompleted = false;
// Get the dynamic properties and previous values defined in qml.
m_properties = propertyAndValues(this);
qCDebug(cfLog) << "properties" << m_properties;

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto objectType = new QQmlOpenMetaObjectType(&DConfigWrapper::staticMetaObject, qmlEngine(this));
#else
auto objectType = new QQmlOpenMetaObjectType(&DConfigWrapper::staticMetaObject);
#endif
m_metaObject = new DConfigWrapperMetaObject(this, objectType);
m_metaObject->setCached(true);

if (m_proxyTarget) {
initWithProxyTarget();
} else {
initWithDConfig();
}
}

bool DConfigWrapper::isDefaultValue(const QString &key) const {
Q_ASSERT(m_proxyTarget);
if (!f_isDefaultValue.enclosingMetaObject())
return false;

return f_isDefaultValue.invoke(m_proxyTarget, Qt::DirectConnection, Q_ARG(QString, key));
}

void DConfigWrapper::initWithProxyTarget()
{
Q_ASSERT(m_proxyTarget);
if (impl) {
impl->deleteLater();
impl = nullptr;
}

qCInfo(cfLog) << QString("Initialize dconfig with proxy object:") << m_proxyTarget;

auto mo = m_proxyTarget->metaObject();
int index = mo->indexOfMethod(QMetaObject::normalizedSignature("isDefaultValue(const QString&)"));

if (index < 0) {
qmlWarning(this) << "Can't know whether the value is default, because the proxyTarget doesn't have isDefaultValue method.";
} else {
f_isDefaultValue = mo->method(index);
}

for (auto iter = m_properties.begin(); iter != m_properties.end(); iter++) {
// it's need to emit signal, because other qml object maybe read the old value
// when binding the property before the component completed, also it has a performance problem.
// sync backend's value to `Wrapper`, we only use Wrapper's value(defined in qml) as fallback value.
if (isDefaultValue(iter.key()))
m_metaObject->setValue(iter.key(), iter.value());
else
m_metaObject->setValue(iter.key(), m_proxyTarget->property(iter.key()));
}

Q_ASSERT(!m_proxyTargetValueChangedConnection);
m_proxyTargetValueChangedConnection = connect(m_proxyTarget,
SIGNAL(valueChanged(QString, QVariant)),
this, SLOT(onProxyTargetValueChanged(QString,QVariant)));

if (!m_proxyTargetValueChangedConnection) {
if (auto engine = qmlEngine(this))
engine->throwError(QString(QLatin1String("Can't connect to valueChanged signal, proxyTarget is invalid.")));
else
qCWarning(cfLog) << "Can't connect to valueChanged signal, proxyTarget is invalid.";
}
}

void DConfigWrapper::initWithDConfig()
{
Q_ASSERT(!impl);
impl = new DTK_CORE_NAMESPACE::DConfig(m_name, m_subpath, this);
Q_ASSERT(!m_proxyTarget);

if (!impl->isValid()) {
qCWarning(cfLog) << QString("create dconfig failed, valid:%1, name:%2, subpath:%3, backend:%4")
.arg(impl->isValid())
.arg(impl->name())
.arg(impl->subpath())
.arg(impl->backendName());
.arg(impl->isValid())
.arg(impl->name())
.arg(impl->subpath())
.arg(impl->backendName());
impl->deleteLater();
impl = nullptr;
return;
}

qInfo() << QString("create dconfig successful, valid:%1, name:%2, subpath:%3, backend:%4")
.arg(impl->isValid())
.arg(impl->name())
.arg(impl->subpath())
.arg(impl->backendName());
qCInfo(cfLog) << QString("create dconfig successful, valid:%1, name:%2, subpath:%3, backend:%4")
.arg(impl->isValid())
.arg(impl->name())
.arg(impl->subpath())
.arg(impl->backendName());

// Get the dynamic properties and previous values defined in qml.
const DefalutProperties &properties = propertyAndValues(this);
qCDebug(cfLog) << "properties" << properties;

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto objectType = new QQmlOpenMetaObjectType(&DConfigWrapper::staticMetaObject, qmlEngine(this));
#else
auto objectType = new QQmlOpenMetaObjectType(&DConfigWrapper::staticMetaObject);
#endif
auto mo = new DConfigWrapperMetaObject(this, objectType);
mo->setCached(true);

for (auto iter = properties.begin(); iter != properties.end(); iter++) {
for (auto iter = m_properties.begin(); iter != m_properties.end(); iter++) {
// it's need to emit signal, because other qml object maybe read the old value
// when binding the property before the component completed, also it has a performance problem.
// sync backend's value to `Wrapper`, we only use Wrapper's value(defined in qml) as fallback value.
mo->setValue(iter.key(), impl->value(iter.key(), iter.value()));
m_metaObject->setValue(iter.key(), impl->value(iter.key(), iter.value()));
}

// Using QueuedConnection because impl->setValue maybe emit sync signal in `propertyWriteValue`.
connect(impl, &DTK_CORE_NAMESPACE::DConfig::valueChanged, this, [this, mo, properties](const QString &key){
// Using QueuedConnection because impl->setValue maybe emit sync signal in `propertyWriteValue`.
connect(impl, &DTK_CORE_NAMESPACE::DConfig::valueChanged, this, [this](const QString &key){
const QByteArray &proName = key.toLocal8Bit();
if (properties.contains(proName)) {
if (m_properties.contains(proName)) {
qCDebug(cfLog) << "update value from DConfig by 'valueChanged', key:" << proName;
mo->setValue(proName, impl->value(proName, properties.value(proName)));
m_metaObject->setValue(proName, impl->value(proName, m_properties.value(proName)));
}
Q_EMIT valueChanged(key);
}, Qt::QueuedConnection);
}

void DConfigWrapper::onProxyTargetValueChanged(const QString &key, const QVariant &value)
{
const QByteArray &propName = key.toLocal8Bit();
if (m_properties.contains(propName)) {
qCDebug(cfLog) << "update value from:" << m_proxyTarget << "by valueChanged', key:" << propName
<< "value:" << value;
if (isDefaultValue(propName))
m_metaObject->setValue(propName, m_properties.value(propName));
else
m_metaObject->setValue(propName, value);
}
Q_EMIT valueChanged(key);
}
22 changes: 21 additions & 1 deletion src/private/dconfigwrapper_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QString subpath READ subpath WRITE setSubpath)
Q_PROPERTY(QObject *proxyTarget READ proxyTarget WRITE setProxyTarget NOTIFY proxyTargetChanged)
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QML_NAMED_ELEMENT(Config)
#endif
Expand All @@ -33,7 +34,10 @@
QString subpath() const;
void setSubpath(const QString &subpath);

QObject *proxyTarget() const;
void setProxyTarget(QObject *newProxyTarget);

public Q_SLOTS:

Check warning on line 40 in src/private/dconfigwrapper_p.h

View workflow job for this annotation

GitHub Actions / cppcheck

There is an unknown macro here somewhere. Configuration is required. If Q_SLOTS is a macro then please configure it.
QVariant value(const QString &key, const QVariant &fallback = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
void resetValue(const QString &key);
Expand All @@ -42,16 +46,32 @@

Q_SIGNALS:
void valueChanged(const QString &key);
void proxyTargetChanged();

public:
virtual void classBegin() override;
virtual void componentComplete() override;

using DefalutProperties = QMap<QByteArray, QVariant>;

private:
bool isDefaultValue(const QString &key) const;
void initWithProxyTarget();
void initWithDConfig();
Q_SLOT void onProxyTargetValueChanged(const QString &key, const QVariant &value);

friend DConfigWrapperMetaObject;
DTK_CORE_NAMESPACE::DConfig *impl;
bool m_componentCompleted = false;
DConfigWrapperMetaObject *m_metaObject = nullptr;
DefalutProperties m_properties;

DTK_CORE_NAMESPACE::DConfig *impl = nullptr;
QString m_name;
QString m_subpath;

QObject *m_proxyTarget = nullptr;
QMetaObject::Connection m_proxyTargetValueChangedConnection;
QMetaMethod f_isDefaultValue;
Q_DISABLE_COPY(DConfigWrapper)
};

Expand Down
Loading