├── .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 | --------------------------------------------------------------------------------