├── .gitignore
├── README.md
├── propertymap.qrc
├── StaticPropertyMap
└── StaticPropertyMap.cpp
├── main.cpp
├── Tank.qml
├── propertymap.pro
├── Player.h
├── Scene.qml
├── QmlPropertyMap
├── QmlPropertyMap.h
├── QmlOpenMetaObject.h
├── QmlPropertyMap.cpp
└── QmlOpenMetaObject.cpp
├── QuickPropertyMap
├── QuickPropertyMap.h
└── QuickPropertyMap.cpp
└── Player.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.user
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PropertyMap
2 |
3 | PropertyMap playground source code for Qt World Summit 2019 speech "Fast C++-to-QML properties".
4 |
--------------------------------------------------------------------------------
/propertymap.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | Scene.qml
4 | Tank.qml
5 |
6 |
7 |
--------------------------------------------------------------------------------
/StaticPropertyMap/StaticPropertyMap.cpp:
--------------------------------------------------------------------------------
1 | #include "StaticPropertyMap.h"
2 |
3 | StaticPropertyMap::StaticPropertyMap(QObject* parent)
4 | : QObject(parent)
5 | {}
6 |
7 | void StaticPropertyMap::insert(const QByteArray& k, const QVariant& v)
8 | {
9 | setProperty(k, v);
10 | }
11 |
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "Player.h"
3 |
4 | int main(int argc, char *argv[])
5 | {
6 | QGuiApplication a(argc, argv);
7 |
8 | Player viewer;
9 |
10 | viewer.setTitle("PropertyMap playground");
11 | viewer.setSource(QUrl("qrc:/Scene.qml"));
12 | viewer.showFullScreen();
13 |
14 | return a.exec();
15 | }
16 |
--------------------------------------------------------------------------------
/Tank.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 |
3 | Rectangle {
4 | property alias gunRotation: gun.rotation
5 |
6 | color: "yellow"
7 | antialiasing: true
8 |
9 | width: 70
10 | height: 50
11 |
12 | Rectangle {
13 | id: gun
14 |
15 | color: "red"
16 | antialiasing: true
17 |
18 | anchors.centerIn: parent
19 | anchors.horizontalCenterOffset: 30
20 | transformOrigin: Item.Left
21 |
22 | width: 60
23 | height: 15
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/propertymap.pro:
--------------------------------------------------------------------------------
1 | QT += quick qml
2 | QT += core-private # for QMetaObjectBuilder
3 | QT += qml-private # for QQmlOpenMetaObject
4 |
5 | CONFIG += c++11
6 |
7 | SOURCES += \
8 | main.cpp \
9 | Player.cpp \
10 | QuickPropertyMap/QuickPropertyMap.cpp \
11 | QmlPropertyMap/QmlPropertyMap.cpp \
12 | QmlPropertyMap/QmlOpenMetaObject.cpp \
13 | StaticPropertyMap/StaticPropertyMap.cpp \
14 |
15 | HEADERS += \
16 | Player.h \
17 | QuickPropertyMap/QuickPropertyMap.h \
18 | QmlPropertyMap/QmlPropertyMap.h \
19 | QmlPropertyMap/QmlOpenMetaObject.h \
20 | StaticPropertyMap/StaticPropertyMap.h \
21 |
22 | RESOURCES += \
23 | propertymap.qrc
24 |
--------------------------------------------------------------------------------
/Player.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class QTimer;
6 |
7 | class StaticPropertyMap;
8 | class QmlPropertyMap;
9 | class QuickPropertyMap;
10 |
11 | //using PropertyMap = StaticPropertyMap;
12 | //using PropertyMap = QmlPropertyMap;
13 | using PropertyMap = QuickPropertyMap;
14 |
15 | class Player : public QQuickView
16 | {
17 | Q_OBJECT
18 |
19 | public:
20 | explicit Player(QWindow* parent = nullptr);
21 |
22 | public:
23 | void advance();
24 |
25 | public:
26 | void test1();
27 | void test2();
28 | void test3();
29 |
30 | private:
31 | PropertyMap* m_propertyMap;
32 | QVector m_speed;
33 | QVector> m_data;
34 | QTimer* m_timer;
35 | int m_step;
36 | };
37 |
--------------------------------------------------------------------------------
/Scene.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 |
3 | Rectangle {
4 | id: scene
5 | color: "black"
6 |
7 | property real a: 0.5
8 |
9 | focus: true
10 |
11 | property int index: 0
12 |
13 | Keys.onPressed: {
14 | if (event.key === Qt.Key_A)
15 | ngi["x_"+index] -= 1
16 | else if (event.key === Qt.Key_D)
17 | ngi["x_"+index] += 1
18 | else if (event.key === Qt.Key_W)
19 | ngi["y_"+index] -= 1
20 | else if (event.key === Qt.Key_S)
21 | ngi["y_"+index] += 1
22 | else if (event.key === Qt.Key_E)
23 | ngi["r_"+index] += 1
24 | else if (event.key === Qt.Key_Q)
25 | ngi["r_"+index] -= 1
26 | }
27 |
28 | Text {
29 | id: fps
30 | text: ngi.fps
31 | font.pointSize: 108
32 | font.bold: true
33 | anchors.centerIn: parent
34 | color: "magenta"
35 | z: 1
36 | }
37 |
38 | Text {
39 | text: "%1: %2 properties".arg(ngi.title).arg(ngi.count * 3)
40 | font.pointSize: 42
41 | font.bold: true
42 |
43 | anchors {
44 | top: parent.top
45 | left: parent.left
46 | topMargin: 20
47 | leftMargin: 20
48 | }
49 |
50 | color: "magenta"
51 | z: 1
52 | }
53 |
54 | Repeater {
55 | model: ngi.count
56 |
57 | Tank {
58 | scale: 0.5
59 | x: (scene.width - width ) * 0.5 + ngi["x_%1".arg(index)] * a
60 | y: (scene.height - height) * 0.5 + ngi["y_%1".arg(index)] * a
61 | rotation: ngi["r_%1".arg(index)]
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/QmlPropertyMap/QmlPropertyMap.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | class QmlPropertyMapPrivate;
11 | class QmlPropertyMap : public QObject
12 | {
13 | Q_OBJECT
14 |
15 | public:
16 | using FastData = QHash;
17 | using PairData = QVector>;
18 |
19 | public:
20 | explicit QmlPropertyMap(QObject *parent = Q_NULLPTR);
21 | virtual ~QmlPropertyMap();
22 |
23 | QVariant value(const QString &key) const;
24 | void clear(const QString &key);
25 |
26 | void insert(const QString &key, const QVariant &value);
27 | void insert(const QVariantMap &data);
28 | void insert(const FastData& data);
29 | void insert(const PairData& data);
30 |
31 | void setCached(bool cached);
32 |
33 | Q_INVOKABLE const QStringList& keys() const;
34 |
35 | int count() const;
36 | int size() const;
37 | bool isEmpty() const;
38 | bool contains(const QString &key) const;
39 |
40 | QVariant &operator[](const QString &key);
41 | QVariant operator[](const QString &key) const;
42 |
43 | Q_SIGNALS:
44 | void valueChanged(const QString &key, const QVariant &value);
45 |
46 | protected:
47 | virtual QVariant updateValue(const QString &key, const QVariant &input);
48 |
49 | template
50 | QmlPropertyMap(DerivedType *derived, QObject *parentObj)
51 | : QObject(*allocatePrivate(), parentObj)
52 | {
53 | Q_UNUSED(derived)
54 | init(&DerivedType::staticMetaObject);
55 | }
56 |
57 | private:
58 | void init(const QMetaObject *staticMetaObject);
59 | static QObjectPrivate *allocatePrivate();
60 |
61 | Q_DECLARE_PRIVATE(QmlPropertyMap)
62 | Q_DISABLE_COPY(QmlPropertyMap)
63 | };
64 |
--------------------------------------------------------------------------------
/QmlPropertyMap/QmlOpenMetaObject.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | class QQmlEngine;
11 | class QMetaPropertyBuilder;
12 | class QmlOpenMetaObjectTypePrivate;
13 | class QmlOpenMetaObjectType : public QQmlRefCount, public QQmlCleanup
14 | {
15 | public:
16 | QmlOpenMetaObjectType(const QMetaObject *base, QQmlEngine *engine);
17 | ~QmlOpenMetaObjectType();
18 |
19 | void createProperties(const QVector &names);
20 | int createProperty(const QByteArray &name);
21 |
22 | int propertyOffset() const;
23 | int signalOffset() const;
24 |
25 | int propertyCount() const;
26 | QByteArray propertyName(int) const;
27 | QMetaObject *metaObject() const;
28 |
29 | protected:
30 | virtual void propertyCreated(int, QMetaPropertyBuilder &);
31 | virtual void clear();
32 |
33 | private:
34 | QmlOpenMetaObjectTypePrivate *d;
35 | friend class QmlOpenMetaObject;
36 | friend class QmlOpenMetaObjectPrivate;
37 | };
38 |
39 | class QmlOpenMetaObjectPrivate;
40 | class QmlOpenMetaObject : public QAbstractDynamicMetaObject
41 | {
42 | public:
43 | QmlOpenMetaObject(QObject *, const QMetaObject * = 0, bool = true);
44 | QmlOpenMetaObject(QObject *, QmlOpenMetaObjectType *, bool = true);
45 | ~QmlOpenMetaObject();
46 |
47 | QVariant value(const QByteArray &) const;
48 | void setValues(const QHash &);
49 | void setValues(const QVector > &);
50 | bool setValue(const QByteArray &, const QVariant &);
51 | QVariant value(int) const;
52 | void setValue(int, const QVariant &);
53 | QVariant &operator[](const QByteArray &);
54 | QVariant &operator[](int);
55 | bool hasValue(int) const;
56 |
57 | int count() const;
58 | QByteArray name(int) const;
59 |
60 | QObject *object() const;
61 | virtual QVariant initialValue(int);
62 |
63 | // Be careful - once setCached(true) is called createProperty() is no
64 | // longer automatically called for new properties.
65 | void setCached(bool);
66 |
67 | QmlOpenMetaObjectType *type() const;
68 |
69 | void emitPropertyNotification(const QByteArray &propertyName);
70 |
71 | protected:
72 | virtual int metaCall(QObject *o, QMetaObject::Call _c, int _id, void **_a);
73 | virtual int createProperty(const char *, const char *);
74 | void createProperties(const QVector &names);
75 |
76 | virtual void propertyRead(int);
77 | virtual void propertyWrite(int);
78 | virtual QVariant propertyWriteValue(int, const QVariant &);
79 | virtual void propertyWritten(int);
80 | virtual void propertyCreated(int, QMetaPropertyBuilder &);
81 |
82 | QAbstractDynamicMetaObject *parent() const;
83 |
84 | private:
85 | QmlOpenMetaObjectPrivate *d;
86 | friend class QmlOpenMetaObjectType;
87 | };
88 |
--------------------------------------------------------------------------------
/QuickPropertyMap/QuickPropertyMap.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | class QuickPropertyMapBase : public QObject
8 | {
9 | Q_OBJECT
10 |
11 | public:
12 | explicit QuickPropertyMapBase(QObject* parent = nullptr) : QObject(parent){}
13 | ~QuickPropertyMapBase() override {}
14 |
15 | signals:
16 | void valueChanged(const QByteArray& name, const QVariant& value);
17 | };
18 |
19 | class QuickPropertyMap : public QuickPropertyMapBase
20 | {
21 | // Meta-object system functions
22 | public:
23 | const QMetaObject *metaObject() const override;
24 | void *qt_metacast(const char *) override;
25 | int qt_metacall(QMetaObject::Call, int, void **) override;
26 |
27 | public:
28 | explicit QuickPropertyMap(QObject* parent = nullptr);
29 | ~QuickPropertyMap() override;
30 |
31 | public:
32 | /**
33 | * Appends a property to the property list
34 | * Has no effect after build() is called
35 | *
36 | * Two-argument version uses value.userType() as a property type
37 | *
38 | * Call build() to create a metaobject and finish QuickPropertyMap creation
39 | */
40 | void addProperty(const QByteArray& name, const QVariant& value, int type);
41 | void addProperty(const QByteArray& name, const QVariant& value) { addProperty(name, value, value.userType()); }
42 |
43 | /**
44 | * @brief Builds a QMetaObject from the property list accumulated by addProperty() calls
45 | */
46 | void build();
47 |
48 | public:
49 | /**
50 | * @brief updates property at 'index' with 'value'
51 | * @param index
52 | * @param value
53 | */
54 | void insert(int index, const QVariant& value);
55 | void insert(const QByteArray& name, const QVariant& value) { insert(indexOf(name), value); }
56 |
57 | public:
58 | int count() const { return m_propertyIndex.count(); }
59 | int indexOf(const QByteArray& name) const { return m_propertyIndex.value(name, -1); }
60 | bool contains(const QByteArray& name) const { return m_propertyIndex.contains(name); }
61 | QByteArrayList keys() const { return m_propertyIndex.keys(); }
62 |
63 | const QByteArray& name (int index) const { return m_propertyList[index].name; }
64 | const QVariant& value(int index) const { return m_propertyList[index].value; }
65 | int type (int index) const { return m_propertyList[index].typeId;}
66 |
67 | QVariant value(const QByteArray& name) const { int i = indexOf(name); return (i != -1) ? value(i) : QVariant(); }
68 |
69 | private:
70 | int my_metacall(QMetaObject::Call call, int id, void** argv);
71 | void buildMetaObject();
72 | void writeValue(QVariant& my, const QVariant& value);
73 |
74 | private:
75 | using DynamicProperty = struct {
76 | QByteArray name;
77 | QVariant value;
78 | int typeId;
79 | };
80 |
81 | QMetaObject* m_metaObject = nullptr;
82 | bool m_finalized = false;
83 |
84 | QHash m_propertyIndex;
85 | QVector m_propertyList;
86 | };
87 |
--------------------------------------------------------------------------------
/QuickPropertyMap/QuickPropertyMap.cpp:
--------------------------------------------------------------------------------
1 | #include "QuickPropertyMap.h"
2 | #include
3 |
4 | QuickPropertyMap::QuickPropertyMap(QObject *parent)
5 | : QuickPropertyMapBase(parent)
6 | {
7 | buildMetaObject(); // NOTE: build an empty valid QMetaObject
8 | }
9 |
10 | QuickPropertyMap::~QuickPropertyMap()
11 | {
12 | free(m_metaObject); // NOTE: because of malloc deep inside QMetaObjectBuilder
13 | }
14 |
15 | void QuickPropertyMap::addProperty(const QByteArray& name, const QVariant& value, int type)
16 | {
17 | if (!m_finalized)
18 | m_propertyList.append(DynamicProperty{name, value, type});
19 | }
20 |
21 | void QuickPropertyMap::build()
22 | {
23 | if (!m_finalized)
24 | {
25 | m_finalized = true;
26 | buildMetaObject();
27 | }
28 | }
29 |
30 | void QuickPropertyMap::insert(int i, const QVariant& value)
31 | {
32 | if (i >= 0 && i < m_propertyList.count())
33 | {
34 | DynamicProperty& p = m_propertyList[i];
35 |
36 | if (p.value != value)
37 | {
38 | writeValue(p.value, value);
39 | QMetaObject::activate(this, m_metaObject, i, nullptr);
40 | }
41 | }
42 | }
43 |
44 | void QuickPropertyMap::writeValue(QVariant& my, const QVariant& value)
45 | {
46 | if (my.userType() == value.userType())
47 | my = value;
48 | else
49 | my = QVariant(my.type());
50 | }
51 |
52 | void QuickPropertyMap::buildMetaObject()
53 | {
54 | free(m_metaObject);
55 | QMetaObjectBuilder builder;
56 |
57 | builder.setClassName("QuickPropertyMap");
58 | builder.setSuperClass(&QuickPropertyMapBase::staticMetaObject);
59 |
60 | for (const DynamicProperty& dynamicProperty: m_propertyList)
61 | {
62 | QMetaPropertyBuilder propertyBuilder = builder.addProperty(dynamicProperty.name, QMetaType::typeName(dynamicProperty.typeId));
63 | QMetaMethodBuilder signalBuilder = builder.addSignal(dynamicProperty.name + "сhanged()");
64 |
65 | propertyBuilder.setWritable(true);
66 | propertyBuilder.setNotifySignal(signalBuilder);
67 | }
68 |
69 | m_metaObject = builder.toMetaObject();
70 |
71 | // NOTE: build an index cache for faster lookups
72 | for (int i = 0; i != m_propertyList.count(); ++i)
73 | m_propertyIndex.insert(m_propertyList[i].name, i);
74 | }
75 |
76 | const QMetaObject* QuickPropertyMap::metaObject() const
77 | {
78 | return m_metaObject;
79 | }
80 |
81 | int QuickPropertyMap::my_metacall(QMetaObject::Call call, int id, void** argv)
82 | {
83 | switch (call)
84 | {
85 | case QMetaObject::ReadProperty:
86 | {
87 | const DynamicProperty& property = m_propertyList[id];
88 | QMetaType::construct(property.typeId, argv[0], property.value.data());
89 | }
90 | break;
91 |
92 | case QMetaObject::WriteProperty:
93 | {
94 | DynamicProperty& p = m_propertyList[id];
95 | QVariant value(p.typeId, argv[0]);
96 |
97 | if (p.value != value)
98 | {
99 | writeValue(p.value, value);
100 | QMetaObject::activate(this, m_metaObject, id, nullptr);
101 |
102 | emit valueChanged(p.name, p.value);
103 | }
104 | }
105 | break;
106 |
107 | default: break;
108 | }
109 |
110 | return -1;
111 | }
112 |
113 | int QuickPropertyMap::qt_metacall(QMetaObject::Call call, int id, void** argv)
114 | {
115 | const int realId = id - m_metaObject->propertyOffset();
116 | return (realId >= 0) ? my_metacall(call, realId, argv) : QuickPropertyMapBase::qt_metacall(call, id, argv);
117 | }
118 |
119 | void* QuickPropertyMap::qt_metacast(const char* name)
120 | {
121 | return (strcmp(name, m_metaObject->className()) == 0) ? this : QuickPropertyMapBase::qt_metacast(name);
122 | }
123 |
--------------------------------------------------------------------------------
/Player.cpp:
--------------------------------------------------------------------------------
1 | #include "Player.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | #include "QmlPropertyMap/QmlPropertyMap.h"
11 | #include "QuickPropertyMap/QuickPropertyMap.h"
12 | #include "StaticPropertyMap/StaticPropertyMap.h"
13 |
14 | #define UNUSED_FUNCTION(x) void (*__##x)(x&, const PairData&) = &init; (void)__##x;
15 |
16 | namespace
17 | {
18 | const int COUNT = 1000;
19 | const double R = 6;
20 |
21 | double posX(int i, double angle) { return (i / 3) * R * cos(angle * M_PI / 180) + 10; }
22 | double posY(int i, double angle) { return (i / 3) * R * sin(angle * M_PI / 180) + 10; }
23 |
24 | using PairData = QVector>;
25 |
26 | void init(StaticPropertyMap& pm, const PairData& data)
27 | {
28 | for (const auto& i : data)
29 | pm.insert(i.first, i.second);
30 | }
31 |
32 | void init(QmlPropertyMap& pm, const PairData& data)
33 | {
34 | for (const auto& i : data)
35 | pm.insert(i.first, i.second);
36 | }
37 |
38 | void init(QuickPropertyMap& pm, const PairData& data)
39 | {
40 | for (const auto& i : data)
41 | pm.addProperty(i.first, i.second);
42 |
43 | pm.build();
44 | }
45 | }
46 |
47 | Player::Player(QWindow* parent)
48 | : QQuickView(parent)
49 | , m_propertyMap(new PropertyMap(this))
50 | , m_timer(new QTimer(this))
51 | , m_step(0)
52 | {
53 | UNUSED_FUNCTION(StaticPropertyMap)
54 | UNUSED_FUNCTION(QmlPropertyMap)
55 | UNUSED_FUNCTION(QuickPropertyMap)
56 |
57 | setResizeMode(QQuickView::SizeRootObjectToView);
58 |
59 | m_data.resize(3 * COUNT);
60 | m_speed.resize(COUNT);
61 |
62 | for (int i = 0; i != COUNT; ++i)
63 | {
64 | m_data[3 * i + 0] = {QString("x_%1").arg(i).toLatin1(), 0.0};
65 | m_data[3 * i + 1] = {QString("y_%1").arg(i).toLatin1(), 0.0};
66 | m_data[3 * i + 2] = {QString("r_%1").arg(i).toLatin1(), 0.0};
67 |
68 | m_speed[i] = 1.0 * qrand() / RAND_MAX + 0.1;
69 | }
70 |
71 | m_data.append({QByteArray("count"), COUNT});
72 | m_data.append({QByteArray("fps") , 0 });
73 | m_data.append({QByteArray("title"), m_propertyMap->metaObject()->className()});
74 |
75 | init(*m_propertyMap, m_data);
76 | rootContext()->setContextProperty("ngi", m_propertyMap);
77 |
78 | test1();
79 | test2();
80 | test3();
81 | }
82 |
83 | void Player::test1()
84 | {
85 | connect(m_timer, &QTimer::timeout, this, &Player::advance);
86 | m_timer->start(16);
87 | }
88 |
89 | void Player::test2()
90 | {
91 | connect(m_propertyMap, &PropertyMap::valueChanged, [](const QString& name, const QVariant& value)
92 | {
93 | qDebug() << name << value;
94 | });
95 | }
96 |
97 | void Player::test3()
98 | {
99 | qInfo("feeding %s:", m_propertyMap->metaObject()->className());
100 |
101 | for (int size : QVector{1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000})
102 | {
103 | PairData data(size);
104 | for (int i = 0; i != size; ++i)
105 | data[i] = {QStringLiteral("x_%1").arg(i).toLatin1(), 0.0};
106 |
107 | PropertyMap p;
108 |
109 | QElapsedTimer t;
110 | t.start();
111 |
112 | init(p, data);
113 |
114 | qInfo("%d %lld", size, t.elapsed());
115 | }
116 | }
117 |
118 | void Player::advance()
119 | {
120 | QElapsedTimer t;
121 | t.start();
122 |
123 | ++m_step;
124 |
125 | for (int i = 0; i != COUNT; ++i)
126 | {
127 | double angle = m_step * m_speed[i];
128 |
129 | m_propertyMap->insert(m_data[3 * i + 0].first, posX(i, angle));
130 | m_propertyMap->insert(m_data[3 * i + 1].first, posY(i, angle));
131 | m_propertyMap->insert(m_data[3 * i + 2].first, angle + 90);
132 | }
133 |
134 | if (m_step % 10 == 0)
135 | m_propertyMap->insert("fps", int(1000.0 / (t.nsecsElapsed() / 1000000.0)));
136 | }
137 |
--------------------------------------------------------------------------------
/QmlPropertyMap/QmlPropertyMap.cpp:
--------------------------------------------------------------------------------
1 | #include "QmlPropertyMap.h"
2 | #include "QmlOpenMetaObject.h"
3 |
4 | #include
5 |
6 | #include
7 |
8 | class QmlPropertyMapMetaObject : public QmlOpenMetaObject
9 | {
10 | public:
11 | QmlPropertyMapMetaObject(QmlPropertyMap *obj, QmlPropertyMapPrivate *objPriv, const QMetaObject *staticMetaObject);
12 |
13 | protected:
14 | virtual QVariant propertyWriteValue(int, const QVariant &);
15 | virtual void propertyWritten(int index);
16 | virtual void propertyCreated(int, QMetaPropertyBuilder &);
17 | virtual int createProperty(const char *, const char *);
18 |
19 | const QString &propertyName(int index);
20 |
21 | private:
22 | QmlPropertyMap *map;
23 | QmlPropertyMapPrivate *priv;
24 | };
25 |
26 | class QmlPropertyMapPrivate : public QObjectPrivate
27 | {
28 | Q_DECLARE_PUBLIC(QmlPropertyMap)
29 | public:
30 | QmlPropertyMapMetaObject *mo;
31 | QSet lookup; // speeds up contains()
32 | QStringList keys;
33 |
34 | QVariant updateValue(const QString &key, const QVariant &input);
35 | void emitChanged(const QString &key, const QVariant &value);
36 | bool validKeyName(const QString& name);
37 |
38 | const QString &propertyName(int index) const;
39 | };
40 |
41 | bool QmlPropertyMapPrivate::validKeyName(const QString& name)
42 | {
43 | //The following strings shouldn't be used as property names
44 | return name != QLatin1String("keys")
45 | && name != QLatin1String("valueChanged")
46 | && name != QLatin1String("QObject")
47 | && name != QLatin1String("destroyed")
48 | && name != QLatin1String("deleteLater");
49 | }
50 |
51 | QVariant QmlPropertyMapPrivate::updateValue(const QString &key, const QVariant &input)
52 | {
53 | Q_Q(QmlPropertyMap);
54 | return q->updateValue(key, input);
55 | }
56 |
57 | void QmlPropertyMapPrivate::emitChanged(const QString &key, const QVariant &value)
58 | {
59 | Q_Q(QmlPropertyMap);
60 | emit q->valueChanged(key, value);
61 | }
62 |
63 | const QString &QmlPropertyMapPrivate::propertyName(int index) const
64 | {
65 | Q_ASSERT(index < keys.size());
66 | return keys[index];
67 | }
68 |
69 | QmlPropertyMapMetaObject::QmlPropertyMapMetaObject(QmlPropertyMap *obj, QmlPropertyMapPrivate *objPriv, const QMetaObject *staticMetaObject)
70 | : QmlOpenMetaObject(obj, staticMetaObject)
71 | {
72 | map = obj;
73 | priv = objPriv;
74 | }
75 |
76 | QVariant QmlPropertyMapMetaObject::propertyWriteValue(int index, const QVariant &input)
77 | {
78 | return priv->updateValue(priv->propertyName(index), input);
79 | }
80 |
81 | void QmlPropertyMapMetaObject::propertyWritten(int index)
82 | {
83 | priv->emitChanged(priv->propertyName(index), operator[](index));
84 | }
85 |
86 | void QmlPropertyMapMetaObject::propertyCreated(int, QMetaPropertyBuilder &b)
87 | {
88 | priv->keys.append(QString::fromUtf8(b.name()));
89 | priv->lookup.insert(priv->keys.last());
90 | }
91 |
92 | int QmlPropertyMapMetaObject::createProperty(const char *name, const char *value)
93 | {
94 | if (!priv->validKeyName(QString::fromUtf8(name)))
95 | return -1;
96 | return QmlOpenMetaObject::createProperty(name, value);
97 | }
98 |
99 | QmlPropertyMap::QmlPropertyMap(QObject *parent)
100 | : QObject(*allocatePrivate(), parent)
101 | {
102 | init(metaObject());
103 | }
104 |
105 | QmlPropertyMap::~QmlPropertyMap()
106 | {}
107 |
108 | void QmlPropertyMap::clear(const QString &key)
109 | {
110 | Q_D(QmlPropertyMap);
111 | d->mo->setValue(key.toUtf8(), QVariant());
112 | }
113 |
114 | QVariant QmlPropertyMap::value(const QString &key) const
115 | {
116 | Q_D(const QmlPropertyMap);
117 | return d->mo->value(key.toUtf8());
118 | }
119 |
120 | void QmlPropertyMap::insert(const QString &key, const QVariant &value)
121 | {
122 | Q_D(QmlPropertyMap);
123 |
124 | if (d->validKeyName(key)) {
125 | d->mo->setValue(key.toUtf8(), value);
126 | } else {
127 | qWarning() << "Creating property with name"
128 | << key
129 | << "is not permitted, conflicts with internal symbols.";
130 | }
131 | }
132 |
133 | void QmlPropertyMap::insert(const QVariantMap &data)
134 | {
135 | Q_D(QmlPropertyMap);
136 |
137 | QHash tmp;
138 |
139 | for (QVariantMap::ConstIterator i = data.cbegin(), e = data.cend(); i != e; ++i) {
140 | if (d->validKeyName(i.key())) {
141 | tmp.insert(i.key().toUtf8(), i.value());
142 | } else {
143 | qWarning() << "Creating property with name"
144 | << i.key()
145 | << "is not permitted, conflicts with internal symbols.";
146 | }
147 | }
148 |
149 | d->mo->setValues(tmp);
150 | }
151 |
152 | void QmlPropertyMap::insert(const FastData &data)
153 | {
154 | Q_D(QmlPropertyMap);
155 | d->mo->setValues(data);
156 | }
157 |
158 | void QmlPropertyMap::insert(const PairData &data)
159 | {
160 | Q_D(QmlPropertyMap);
161 | d->mo->setValues(data);
162 | }
163 |
164 | void QmlPropertyMap::setCached(bool cached)
165 | {
166 | Q_D(QmlPropertyMap);
167 | d->mo->setCached(cached);
168 | }
169 |
170 | const QStringList& QmlPropertyMap::keys() const
171 | {
172 | Q_D(const QmlPropertyMap);
173 | return d->keys;
174 | }
175 |
176 | int QmlPropertyMap::count() const
177 | {
178 | Q_D(const QmlPropertyMap);
179 | return d->keys.count();
180 | }
181 |
182 | int QmlPropertyMap::size() const
183 | {
184 | Q_D(const QmlPropertyMap);
185 | return d->keys.size();
186 | }
187 |
188 | bool QmlPropertyMap::isEmpty() const
189 | {
190 | Q_D(const QmlPropertyMap);
191 | return d->keys.isEmpty();
192 | }
193 |
194 | bool QmlPropertyMap::contains(const QString &key) const
195 | {
196 | Q_D(const QmlPropertyMap);
197 | return d->lookup.contains(key);
198 | }
199 |
200 | QVariant &QmlPropertyMap::operator[](const QString &key)
201 | {
202 | //### optimize
203 | Q_D(QmlPropertyMap);
204 | QByteArray utf8key = key.toUtf8();
205 | if (!d->lookup.contains(key))
206 | insert(key, QVariant());//force creation -- needed below
207 |
208 | return (*(d->mo))[utf8key];
209 | }
210 |
211 | QVariant QmlPropertyMap::operator[](const QString &key) const
212 | {
213 | return value(key);
214 | }
215 |
216 | QVariant QmlPropertyMap::updateValue(const QString &key, const QVariant &input)
217 | {
218 | Q_UNUSED(key)
219 | return input;
220 | }
221 |
222 | void QmlPropertyMap::init(const QMetaObject *staticMetaObject)
223 | {
224 | Q_D(QmlPropertyMap);
225 | d->mo = new QmlPropertyMapMetaObject(this, d, staticMetaObject);
226 | }
227 |
228 | QObjectPrivate *QmlPropertyMap::allocatePrivate()
229 | {
230 | return new QmlPropertyMapPrivate;
231 | }
232 |
--------------------------------------------------------------------------------
/QmlPropertyMap/QmlOpenMetaObject.cpp:
--------------------------------------------------------------------------------
1 | #include "QmlOpenMetaObject.h"
2 |
3 | #include
4 |
5 | #if QT_VERSION <= QT_VERSION_CHECK(5, 5, 1)
6 | #include
7 | #elif QT_VERSION <= QT_VERSION_CHECK(5, 6, 3)
8 | #include
9 | #include
10 | #endif
11 |
12 | #include
13 | #include
14 |
15 | class QmlOpenMetaObjectTypePrivate
16 | {
17 | public:
18 | QmlOpenMetaObjectTypePrivate() : mem(0), cache(0), engine(0) {}
19 |
20 | void init(const QMetaObject *metaObj);
21 |
22 | int propertyOffset;
23 | int signalOffset;
24 | QHash names;
25 | QMetaObjectBuilder mob;
26 | QMetaObject *mem;
27 | QQmlPropertyCache *cache;
28 | QQmlEngine *engine;
29 | QSet referers;
30 | };
31 |
32 | QmlOpenMetaObjectType::QmlOpenMetaObjectType(const QMetaObject *base, QQmlEngine *engine)
33 | : QQmlCleanup(engine), d(new QmlOpenMetaObjectTypePrivate)
34 | {
35 | d->engine = engine;
36 | d->init(base);
37 | }
38 |
39 | QmlOpenMetaObjectType::~QmlOpenMetaObjectType()
40 | {
41 | if (d->mem)
42 | free(d->mem);
43 | if (d->cache)
44 | d->cache->release();
45 | delete d;
46 | }
47 |
48 | void QmlOpenMetaObjectType::clear()
49 | {
50 | d->engine = 0;
51 | }
52 |
53 | int QmlOpenMetaObjectType::propertyOffset() const
54 | {
55 | return d->propertyOffset;
56 | }
57 |
58 | int QmlOpenMetaObjectType::signalOffset() const
59 | {
60 | return d->signalOffset;
61 | }
62 |
63 | int QmlOpenMetaObjectType::propertyCount() const
64 | {
65 | return d->names.count();
66 | }
67 |
68 | QByteArray QmlOpenMetaObjectType::propertyName(int idx) const
69 | {
70 | Q_ASSERT(idx >= 0 && idx < d->names.count());
71 |
72 | return d->mob.property(idx).name();
73 | }
74 |
75 | QMetaObject *QmlOpenMetaObjectType::metaObject() const
76 | {
77 | return d->mem;
78 | }
79 |
80 | void QmlOpenMetaObjectType::createProperties(const QVector &names)
81 | {
82 | for (int i = 0; i < names.count(); ++i) {
83 | const QByteArray &name = names.at(i);
84 | const int id = d->mob.propertyCount();
85 | d->mob.addSignal("__" + QByteArray::number(id) + "()");
86 | QMetaPropertyBuilder build = d->mob.addProperty(name, "QVariant", id);
87 | propertyCreated(id, build);
88 | d->names.insert(name, id);
89 | }
90 | free(d->mem);
91 | d->mem = d->mob.toMetaObject();
92 | QSet::iterator it = d->referers.begin();
93 | while (it != d->referers.end()) {
94 | QmlOpenMetaObject *omo = *it;
95 | *static_cast(omo) = *d->mem;
96 | if (d->cache)
97 | d->cache->update(omo);
98 | ++it;
99 | }
100 | }
101 |
102 | int QmlOpenMetaObjectType::createProperty(const QByteArray &name)
103 | {
104 | int id = d->mob.propertyCount();
105 | d->mob.addSignal("__" + QByteArray::number(id) + "()");
106 | QMetaPropertyBuilder build = d->mob.addProperty(name, "QVariant", id);
107 | propertyCreated(id, build);
108 | free(d->mem);
109 | d->mem = d->mob.toMetaObject();
110 | d->names.insert(name, id);
111 | QSet::iterator it = d->referers.begin();
112 | while (it != d->referers.end()) {
113 | QmlOpenMetaObject *omo = *it;
114 | *static_cast(omo) = *d->mem;
115 | if (d->cache)
116 | d->cache->update(omo);
117 | ++it;
118 | }
119 |
120 | return d->propertyOffset + id;
121 | }
122 |
123 | void QmlOpenMetaObjectType::propertyCreated(int id, QMetaPropertyBuilder &builder)
124 | {
125 | if (d->referers.count())
126 | (*d->referers.begin())->propertyCreated(id, builder);
127 | }
128 |
129 | void QmlOpenMetaObjectTypePrivate::init(const QMetaObject *metaObj)
130 | {
131 | if (!mem) {
132 | mob.setSuperClass(metaObj);
133 | mob.setClassName(metaObj->className());
134 | mob.setFlags(QMetaObjectBuilder::DynamicMetaObject);
135 |
136 | mem = mob.toMetaObject();
137 |
138 | propertyOffset = mem->propertyOffset();
139 | signalOffset = mem->methodOffset();
140 | }
141 | }
142 |
143 | class QmlOpenMetaObjectPrivate
144 | {
145 | public:
146 | QmlOpenMetaObjectPrivate(QmlOpenMetaObject *_q)
147 | : q(_q), parent(0), type(0), cacheProperties(false) {}
148 |
149 | inline QPair &getDataRef(int idx) {
150 | while (data.count() <= idx)
151 | data << QPair(QVariant(), false);
152 | return data[idx];
153 | }
154 |
155 | inline QVariant &getData(int idx) {
156 | QPair &prop = getDataRef(idx);
157 | if (!prop.second) {
158 | prop.first = q->initialValue(idx);
159 | prop.second = true;
160 | }
161 | return prop.first;
162 | }
163 |
164 | inline bool hasData(int idx) const {
165 | if (idx >= data.count())
166 | return false;
167 | return data[idx].second;
168 | }
169 |
170 | bool autoCreate;
171 | QmlOpenMetaObject *q;
172 | QAbstractDynamicMetaObject *parent;
173 | QList > data;
174 | QObject *object;
175 | QmlOpenMetaObjectType *type;
176 | bool cacheProperties;
177 | };
178 |
179 | QmlOpenMetaObject::QmlOpenMetaObject(QObject *obj, const QMetaObject *base, bool automatic)
180 | : d(new QmlOpenMetaObjectPrivate(this))
181 | {
182 | d->autoCreate = automatic;
183 | d->object = obj;
184 |
185 | d->type = new QmlOpenMetaObjectType(base ? base : obj->metaObject(), 0);
186 | d->type->d->referers.insert(this);
187 |
188 | QObjectPrivate *op = QObjectPrivate::get(obj);
189 | d->parent = static_cast(op->metaObject);
190 | *static_cast(this) = *d->type->d->mem;
191 | op->metaObject = this;
192 | }
193 |
194 | QmlOpenMetaObject::QmlOpenMetaObject(QObject *obj, QmlOpenMetaObjectType *type, bool automatic)
195 | : d(new QmlOpenMetaObjectPrivate(this))
196 | {
197 | d->autoCreate = automatic;
198 | d->object = obj;
199 |
200 | d->type = type;
201 | d->type->addref();
202 | d->type->d->referers.insert(this);
203 |
204 | QObjectPrivate *op = QObjectPrivate::get(obj);
205 | d->parent = static_cast(op->metaObject);
206 | *static_cast(this) = *d->type->d->mem;
207 | op->metaObject = this;
208 | }
209 |
210 | QmlOpenMetaObject::~QmlOpenMetaObject()
211 | {
212 | if (d->parent)
213 | delete d->parent;
214 | d->type->d->referers.remove(this);
215 | d->type->release();
216 | delete d;
217 | }
218 |
219 | QmlOpenMetaObjectType *QmlOpenMetaObject::type() const
220 | {
221 | return d->type;
222 | }
223 |
224 | void QmlOpenMetaObject::emitPropertyNotification(const QByteArray &propertyName)
225 | {
226 | QHash::ConstIterator iter = d->type->d->names.constFind(propertyName);
227 | if (iter == d->type->d->names.constEnd())
228 | return;
229 | activate(d->object, *iter + d->type->d->signalOffset, 0);
230 | }
231 |
232 | int QmlOpenMetaObject::metaCall(QObject *o, QMetaObject::Call c, int id, void **a)
233 | {
234 | Q_ASSERT(d->object == o);
235 |
236 | if (( c == QMetaObject::ReadProperty || c == QMetaObject::WriteProperty)
237 | && id >= d->type->d->propertyOffset) {
238 | int propId = id - d->type->d->propertyOffset;
239 | if (c == QMetaObject::ReadProperty) {
240 | propertyRead(propId);
241 | *reinterpret_cast(a[0]) = d->getData(propId);
242 | } else if (c == QMetaObject::WriteProperty) {
243 | if (propId >= d->data.count() || d->data.at(propId).first != *reinterpret_cast(a[0])) {
244 | propertyWrite(propId);
245 | QPair &prop = d->getDataRef(propId);
246 | prop.first = propertyWriteValue(propId, *reinterpret_cast(a[0]));
247 | prop.second = true;
248 | propertyWritten(propId);
249 | activate(o, d->type->d->signalOffset + propId, 0);
250 | }
251 | }
252 | return -1;
253 | } else {
254 | if (d->parent)
255 | return d->parent->metaCall(o, c, id, a);
256 | else
257 | return o->qt_metacall(c, id, a);
258 | }
259 | }
260 |
261 | QAbstractDynamicMetaObject *QmlOpenMetaObject::parent() const
262 | {
263 | return d->parent;
264 | }
265 |
266 | QVariant QmlOpenMetaObject::value(int id) const
267 | {
268 | return d->getData(id);
269 | }
270 |
271 | void QmlOpenMetaObject::setValue(int id, const QVariant &value)
272 | {
273 | QPair &prop = d->getDataRef(id);
274 | prop.first = propertyWriteValue(id, value);
275 | prop.second = true;
276 | activate(d->object, id + d->type->d->signalOffset, 0);
277 | }
278 |
279 | QVariant QmlOpenMetaObject::value(const QByteArray &name) const
280 | {
281 | QHash::ConstIterator iter = d->type->d->names.constFind(name);
282 | if (iter == d->type->d->names.cend())
283 | return QVariant();
284 |
285 | return d->getData(*iter);
286 | }
287 |
288 | QVariant &QmlOpenMetaObject::operator[](const QByteArray &name)
289 | {
290 | QHash::ConstIterator iter = d->type->d->names.constFind(name);
291 | Q_ASSERT(iter != d->type->d->names.cend());
292 |
293 | return d->getData(*iter);
294 | }
295 |
296 | QVariant &QmlOpenMetaObject::operator[](int id)
297 | {
298 | return d->getData(id);
299 | }
300 |
301 | void QmlOpenMetaObject::setValues(const QHash &data)
302 | {
303 | QVector newProperties;
304 |
305 | for (auto i = data.cbegin(), e = data.cend(); i != e; ++i) {
306 | auto iter = d->type->d->names.constFind(i.key());
307 |
308 | if (iter == d->type->d->names.cend())
309 | newProperties << i.key();
310 | }
311 |
312 | if (!newProperties.isEmpty())
313 | createProperties(newProperties);
314 |
315 | for (auto i = data.cbegin(), e = data.cend(); i != e; ++i) {
316 | auto iter = d->type->d->names.constFind(i.key());
317 |
318 | if (iter != d->type->d->names.cend()) {
319 | int id = *iter;
320 | const QVariant& val = i.value();
321 |
322 | QVariant &dataVal = d->getData(id);
323 | if (dataVal != val) {
324 | dataVal = val;
325 | activate(d->object, id + d->type->d->signalOffset, 0);
326 | }
327 | }
328 | }
329 | }
330 |
331 | void QmlOpenMetaObject::setValues(const QVector>& data)
332 | {
333 | QVector newProperties;
334 |
335 | for (auto i = data.cbegin(), e = data.cend(); i != e; ++i) {
336 | QHash::ConstIterator iter = d->type->d->names.constFind(i->first);
337 |
338 | if (iter == d->type->d->names.cend())
339 | newProperties << i->first;
340 | }
341 |
342 | if (!newProperties.isEmpty())
343 | createProperties(newProperties);
344 |
345 | for (auto i = data.cbegin(), e = data.cend(); i != e; ++i) {
346 | auto iter = d->type->d->names.constFind(i->first);
347 |
348 | if (iter != d->type->d->names.cend()) {
349 | int id = *iter;
350 | const QVariant& val = i->second;
351 |
352 | QVariant &dataVal = d->getData(id);
353 | if (dataVal != val) {
354 | dataVal = val;
355 | activate(d->object, id + d->type->d->signalOffset, 0);
356 | }
357 | }
358 | }
359 | }
360 |
361 | bool QmlOpenMetaObject::setValue(const QByteArray &name, const QVariant &val)
362 | {
363 | QHash::ConstIterator iter = d->type->d->names.constFind(name);
364 |
365 | int id = -1;
366 | if (iter == d->type->d->names.cend()) {
367 | id = createProperty(name.constData(), "") - d->type->d->propertyOffset;
368 | } else {
369 | id = *iter;
370 | }
371 |
372 | if (id >= 0) {
373 | QVariant &dataVal = d->getData(id);
374 | if (dataVal == val)
375 | return false;
376 |
377 | dataVal = val;
378 | activate(d->object, id + d->type->d->signalOffset, 0);
379 | return true;
380 | }
381 |
382 | return false;
383 | }
384 |
385 | // returns true if this value has been initialized by a call to either value() or setValue()
386 | bool QmlOpenMetaObject::hasValue(int id) const
387 | {
388 | return d->hasData(id);
389 | }
390 |
391 | void QmlOpenMetaObject::setCached(bool c)
392 | {
393 | if (c == d->cacheProperties || !d->type->d->engine)
394 | return;
395 |
396 | d->cacheProperties = c;
397 |
398 | QQmlData *qmldata = QQmlData::get(d->object, true);
399 | if (d->cacheProperties) {
400 | if (!d->type->d->cache)
401 | #if QT_VERSION <= QT_VERSION_CHECK(5, 5, 1)
402 | d->type->d->cache = new QQmlPropertyCache(d->type->d->engine, this);
403 | #elif QT_VERSION <= QT_VERSION_CHECK(5, 6, 3)
404 | d->type->d->cache = new QQmlPropertyCache(QV8Engine::getV4(d->type->d->engine), this);
405 | #else
406 | d->type->d->cache = new QQmlPropertyCache(this);
407 | #endif
408 | qmldata->propertyCache = d->type->d->cache;
409 | d->type->d->cache->addref();
410 | } else {
411 | if (d->type->d->cache)
412 | d->type->d->cache->release();
413 | qmldata->propertyCache = 0;
414 | }
415 | }
416 |
417 |
418 | int QmlOpenMetaObject::createProperty(const char *name, const char *)
419 | {
420 | if (d->autoCreate) {
421 | int result = d->type->createProperty(name);
422 |
423 | if (QQmlData *ddata = QQmlData::get(d->object, /*create*/false)) {
424 | if (ddata->propertyCache) {
425 | ddata->propertyCache->release();
426 | ddata->propertyCache = 0;
427 | }
428 | }
429 |
430 | return result;
431 | } else
432 | return -1;
433 | }
434 |
435 | void QmlOpenMetaObject::createProperties(const QVector &names)
436 | {
437 | if (d->autoCreate) {
438 | d->type->createProperties(names);
439 |
440 | if (QQmlData *ddata = QQmlData::get(d->object, /*create*/false)) {
441 | if (ddata->propertyCache) {
442 | ddata->propertyCache->release();
443 | ddata->propertyCache = 0;
444 | }
445 | }
446 | }
447 | }
448 |
449 | void QmlOpenMetaObject::propertyRead(int)
450 | {
451 | }
452 |
453 | void QmlOpenMetaObject::propertyWrite(int)
454 | {
455 | }
456 |
457 | QVariant QmlOpenMetaObject::propertyWriteValue(int, const QVariant &value)
458 | {
459 | return value;
460 | }
461 |
462 | void QmlOpenMetaObject::propertyWritten(int)
463 | {
464 | }
465 |
466 | void QmlOpenMetaObject::propertyCreated(int, QMetaPropertyBuilder &)
467 | {
468 | }
469 |
470 | QVariant QmlOpenMetaObject::initialValue(int)
471 | {
472 | return QVariant();
473 | }
474 |
475 | int QmlOpenMetaObject::count() const
476 | {
477 | return d->type->d->names.count();
478 | }
479 |
480 | QByteArray QmlOpenMetaObject::name(int idx) const
481 | {
482 | Q_ASSERT(idx >= 0 && idx < d->type->d->names.count());
483 |
484 | return d->type->d->mob.property(idx).name();
485 | }
486 |
487 | QObject *QmlOpenMetaObject::object() const
488 | {
489 | return d->object;
490 | }
491 |
--------------------------------------------------------------------------------