From 90f6e74e7cf40c4d3cf8e7cb592a92ba495661e1 Mon Sep 17 00:00:00 2001 From: Paul Knopf Date: Mon, 15 Apr 2019 19:30:21 -0400 Subject: [PATCH] Implemented inheritance. --- src/native/QmlNet/QmlNet/qml/NetValue.cpp | 36 ++++-- .../QmlNet/QmlNet/qml/NetValueMetaObject.cpp | 69 +++++++++-- .../QmlNet/QmlNet/qml/NetValueMetaObject.h | 2 + .../QmlNet/qml/QQmlApplicationEngine.cpp | 56 ++++++++- .../QmlNet/QmlNet/types/NetTypeManager.cpp | 9 ++ .../QmlNet/QmlNet/types/NetTypeManager.h | 1 + src/net/Qml.Net.Sandbox/Program.Tests.cs | 1 - .../Qml.Net.Sandbox/Qml.Net.Sandbox.csproj | 8 +- src/net/Qml.Net.Tests/Qml/SignalTests.cs | 115 ++++++++++++++++-- .../Types/NetTypeManagerTests.cs | 51 ++++++++ src/net/Qml.Net/Internal/DefaultCallbacks.cs | 4 +- src/net/Qml.Net/QCoreApplication.cs | 4 +- src/net/styles.ruleset | 1 + 13 files changed, 314 insertions(+), 43 deletions(-) diff --git a/src/native/QmlNet/QmlNet/qml/NetValue.cpp b/src/native/QmlNet/QmlNet/qml/NetValue.cpp index fa43c729..81219a49 100644 --- a/src/native/QmlNet/QmlNet/qml/NetValue.cpp +++ b/src/native/QmlNet/QmlNet/qml/NetValue.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -133,23 +134,32 @@ NetValue::NetValue(const QSharedPointer& instance, QObject *parent } collection->netValues.append(this); - // Auto wire up all of our signal handlers that will invoke .NET delegates. - for(int index = 0; index <= instance->getTypeInfo()->getSignalCount() - 1; index++) - { - QSharedPointer signalInfo = instance->getTypeInfo()->getSignal(index); + QList> types; - QString signalSig = signalInfo->getSignature(); - QString slotSig = signalInfo->getSlotSignature(); + auto type = instance->getTypeInfo(); - int signalIndex = valueMeta->indexOfSignal(signalSig.toLatin1().data()); - int slotIndex = valueMeta->indexOfSlot(slotSig.toLatin1().data()); + while(type != nullptr) { + types.insert(0, type); + type = NetTypeManager::getBaseType(type); + } - QMetaMethod signalMethod = valueMeta->method(signalIndex); - QMetaMethod slotMethod = valueMeta->method(slotIndex); + for(QSharedPointer type : types) { + for(int index = 0; index <= type->getSignalCount() - 1; index++) { + QSharedPointer signalInfo = type->getSignal(index); - QObject::connect(this, signalMethod, - this, slotMethod); - }; + QString signalSig = signalInfo->getSignature(); + QString slotSig = signalInfo->getSlotSignature(); + + int signalIndex = valueMeta->indexOfSignal(signalSig.toLatin1().data()); + int slotIndex = valueMeta->indexOfSlot(slotSig.toLatin1().data()); + + QMetaMethod signalMethod = valueMeta->method(signalIndex); + QMetaMethod slotMethod = valueMeta->method(slotIndex); + + QObject::connect(this, signalMethod, + this, slotMethod); + } + } } QMap NetValue::objectIdNetValuesMap = QMap(); diff --git a/src/native/QmlNet/QmlNet/qml/NetValueMetaObject.cpp b/src/native/QmlNet/QmlNet/qml/NetValueMetaObject.cpp index 9847d520..cd760702 100644 --- a/src/native/QmlNet/QmlNet/qml/NetValueMetaObject.cpp +++ b/src/native/QmlNet/QmlNet/qml/NetValueMetaObject.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -18,10 +19,17 @@ QMetaObject *metaObjectFor(const QSharedPointer& typeInfo) typeInfo->ensureLoaded(); QMetaObjectBuilder mob; - mob.setSuperClass(&QObject::staticMetaObject); mob.setClassName(typeInfo->getClassName().toLatin1()); mob.setFlags(QMetaObjectBuilder::DynamicMetaObject); + QString baseType = typeInfo->getBaseType(); + if(baseType.isNull() || baseType.isEmpty()) { + mob.setSuperClass(&QObject::staticMetaObject); + } else { + auto baseTypeInfo = NetTypeManager::getTypeInfo(baseType); + mob.setSuperClass(metaObjectFor(baseTypeInfo)); + } + // register all the signals for the type for(int index = 0; index <= typeInfo->getSignalCount() - 1; index++) { @@ -99,16 +107,49 @@ NetValueMetaObject::NetValueMetaObject(QObject *value, } int NetValueMetaObject::metaCall(QMetaObject::Call c, int idx, void **a) +{ +#ifdef QMLNET_TRACE + switch(c) { + case ReadProperty: + { + auto prop = property(idx); + qDebug() << this->className() << ": reading property: " << idx << ": " << prop.name(); + } + break; + case WriteProperty: + { + auto prop = property(idx); + qDebug() << this->className() << ": writing property: " << idx << ": " << prop.name(); + } + break; + case InvokeMetaMethod: + { + auto meth = method(idx); + qDebug() << this->className() << ": invoking method: " << idx << ": " << meth.name(); + } + break; + default: + break; // Unhandled. + } +#endif + return metaCallRecursive(c, idx, idx, a, instance->getTypeInfo()); +} + +int NetValueMetaObject::metaCallRecursive(QMetaObject::Call c, int originalIdx, int idx, void **a, QSharedPointer typeInfo) { switch(c) { case ReadProperty: { int offset = propertyOffset(); if (idx < offset) { + auto baseType = NetTypeManager::getBaseType(typeInfo); + if(baseType != nullptr) { + return metaCallRecursive(c, originalIdx, idx, a, baseType); + } return value->qt_metacall(c, idx, a); } - QSharedPointer propertyInfo = instance->getTypeInfo()->getProperty(idx - offset); + QSharedPointer propertyInfo = typeInfo->getProperty(idx - offset); QSharedPointer propertyType = propertyInfo->getReturnType(); QSharedPointer result = QSharedPointer(new NetVariant()); @@ -133,10 +174,14 @@ int NetValueMetaObject::metaCall(QMetaObject::Call c, int idx, void **a) { int offset = propertyOffset(); if (idx < offset) { + auto baseType = NetTypeManager::getBaseType(typeInfo); + if(baseType != nullptr) { + return metaCallRecursive(c, originalIdx, idx, a, baseType); + } return value->qt_metacall(c, idx, a); } - QSharedPointer propertyInfo = instance->getTypeInfo()->getProperty(idx - offset); + QSharedPointer propertyInfo = typeInfo->getProperty(idx - offset); QSharedPointer propertyType = propertyInfo->getReturnType(); QSharedPointer newValue = QSharedPointer(new NetVariant()); @@ -149,22 +194,26 @@ int NetValueMetaObject::metaCall(QMetaObject::Call c, int idx, void **a) { int offset = methodOffset(); if (idx < offset) { + auto baseType = NetTypeManager::getBaseType(typeInfo); + if(baseType != nullptr) { + return metaCallRecursive(c, originalIdx, idx + ((baseType->getSignalCount() * 2) + baseType->getLocalMethodCount()), a, baseType); + } return value->qt_metacall(c, idx, a); } idx -= offset; - if(idx < instance->getTypeInfo()->getSignalCount()) { + if(idx < typeInfo->getSignalCount()) { // This is a signal call, activate it! - activate(value, idx + offset, a); + activate(value, originalIdx, a); return -1; } - idx -= instance->getTypeInfo()->getSignalCount(); + idx -= typeInfo->getSignalCount(); - if(idx < instance->getTypeInfo()->getLocalMethodCount()) { + if(idx < typeInfo->getLocalMethodCount()) { // This is a method call! - QSharedPointer methodInfo = instance->getTypeInfo()->getLocalMethodInfo(idx); + QSharedPointer methodInfo = typeInfo->getLocalMethodInfo(idx); QSharedPointer parameters = QSharedPointer(new NetVariantList()); for(int index = 0; index <= methodInfo->getParameterCount() - 1; index++) @@ -204,12 +253,12 @@ int NetValueMetaObject::metaCall(QMetaObject::Call c, int idx, void **a) return -1; } - idx -= instance->getTypeInfo()->getLocalMethodCount(); + idx -= typeInfo->getLocalMethodCount(); { // This is a slot invocation, likely the built-in handlers that are used // to trigger NET delegates for any signals. - QSharedPointer signalInfo = instance->getTypeInfo()->getSignal(idx); + QSharedPointer signalInfo = typeInfo->getSignal(idx); QSharedPointer parameters; if(signalInfo->getParameterCount() > 0) { diff --git a/src/native/QmlNet/QmlNet/qml/NetValueMetaObject.h b/src/native/QmlNet/QmlNet/qml/NetValueMetaObject.h index 2b3a0c41..a3d1589f 100644 --- a/src/native/QmlNet/QmlNet/qml/NetValueMetaObject.h +++ b/src/native/QmlNet/QmlNet/qml/NetValueMetaObject.h @@ -17,6 +17,8 @@ protected: int metaCall(QMetaObject::Call c, int id, void **a); private: + int metaCallRecursive(QMetaObject::Call c, int originalIdx, int idx, void **a, QSharedPointer typeInfo); + QObject *value; QSharedPointer instance; }; diff --git a/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.cpp b/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.cpp index f697e46b..a62f0f5c 100644 --- a/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.cpp +++ b/src/native/QmlNet/QmlNet/qml/QQmlApplicationEngine.cpp @@ -191,6 +191,7 @@ Q_DECL_EXPORT int qqmlapplicationengine_registerType(NetTypeInfoContainer* typeC NETVALUETYPE_CASE(114) NETVALUETYPE_CASE(115) NETVALUETYPE_CASE(116) + NETVALUETYPE_CASE(117) NETVALUETYPE_CASE(118) NETVALUETYPE_CASE(119) NETVALUETYPE_CASE(120) @@ -199,8 +200,33 @@ Q_DECL_EXPORT int qqmlapplicationengine_registerType(NetTypeInfoContainer* typeC NETVALUETYPE_CASE(123) NETVALUETYPE_CASE(124) NETVALUETYPE_CASE(125) + NETVALUETYPE_CASE(126) + NETVALUETYPE_CASE(127) + NETVALUETYPE_CASE(128) + NETVALUETYPE_CASE(129) + NETVALUETYPE_CASE(130) + NETVALUETYPE_CASE(131) + NETVALUETYPE_CASE(132) + NETVALUETYPE_CASE(133) + NETVALUETYPE_CASE(134) + NETVALUETYPE_CASE(135) + NETVALUETYPE_CASE(136) + NETVALUETYPE_CASE(137) + NETVALUETYPE_CASE(138) + NETVALUETYPE_CASE(139) + NETVALUETYPE_CASE(140) + NETVALUETYPE_CASE(141) + NETVALUETYPE_CASE(142) + NETVALUETYPE_CASE(143) + NETVALUETYPE_CASE(144) + NETVALUETYPE_CASE(145) + NETVALUETYPE_CASE(146) + NETVALUETYPE_CASE(147) + NETVALUETYPE_CASE(148) + NETVALUETYPE_CASE(149) + NETVALUETYPE_CASE(150) } - qFatal("Too many registered types"); + qFatal("Too many registered types: %d", netValueTypeNumber); return -1; } @@ -335,6 +361,7 @@ Q_DECL_EXPORT int qqmlapplicationengine_registerSingletonTypeNet(NetTypeInfoCont NETVALUETYPESINGLETON_CASE(114) NETVALUETYPESINGLETON_CASE(115) NETVALUETYPESINGLETON_CASE(116) + NETVALUETYPESINGLETON_CASE(117) NETVALUETYPESINGLETON_CASE(118) NETVALUETYPESINGLETON_CASE(119) NETVALUETYPESINGLETON_CASE(120) @@ -343,8 +370,33 @@ Q_DECL_EXPORT int qqmlapplicationengine_registerSingletonTypeNet(NetTypeInfoCont NETVALUETYPESINGLETON_CASE(123) NETVALUETYPESINGLETON_CASE(124) NETVALUETYPESINGLETON_CASE(125) + NETVALUETYPESINGLETON_CASE(126) + NETVALUETYPESINGLETON_CASE(127) + NETVALUETYPESINGLETON_CASE(128) + NETVALUETYPESINGLETON_CASE(129) + NETVALUETYPESINGLETON_CASE(130) + NETVALUETYPESINGLETON_CASE(131) + NETVALUETYPESINGLETON_CASE(132) + NETVALUETYPESINGLETON_CASE(133) + NETVALUETYPESINGLETON_CASE(134) + NETVALUETYPESINGLETON_CASE(135) + NETVALUETYPESINGLETON_CASE(136) + NETVALUETYPESINGLETON_CASE(137) + NETVALUETYPESINGLETON_CASE(138) + NETVALUETYPESINGLETON_CASE(139) + NETVALUETYPESINGLETON_CASE(140) + NETVALUETYPESINGLETON_CASE(141) + NETVALUETYPESINGLETON_CASE(142) + NETVALUETYPESINGLETON_CASE(143) + NETVALUETYPESINGLETON_CASE(144) + NETVALUETYPESINGLETON_CASE(145) + NETVALUETYPESINGLETON_CASE(146) + NETVALUETYPESINGLETON_CASE(147) + NETVALUETYPESINGLETON_CASE(148) + NETVALUETYPESINGLETON_CASE(149) + NETVALUETYPESINGLETON_CASE(150) } - qFatal("Too many registered types"); + qFatal("Too many registered types: %d", netValueTypeNumber); return -1; } diff --git a/src/native/QmlNet/QmlNet/types/NetTypeManager.cpp b/src/native/QmlNet/QmlNet/types/NetTypeManager.cpp index ac19a89c..49d33af9 100644 --- a/src/native/QmlNet/QmlNet/types/NetTypeManager.cpp +++ b/src/native/QmlNet/QmlNet/types/NetTypeManager.cpp @@ -26,6 +26,15 @@ QSharedPointer NetTypeManager::getTypeInfo(const QString& typeName) return typeInfo; } +QSharedPointer NetTypeManager::getBaseType(QSharedPointer typeInfo) +{ + auto baseType = typeInfo->getBaseType(); + if(baseType.isNull() || baseType.isEmpty()) { + return nullptr; + } + return NetTypeManager::getTypeInfo(baseType); +} + extern "C" { Q_DECL_EXPORT NetTypeInfoContainer* type_manager_getTypeInfo(LPWSTR fullTypeName) { diff --git a/src/native/QmlNet/QmlNet/types/NetTypeManager.h b/src/native/QmlNet/QmlNet/types/NetTypeManager.h index 8c309812..10403224 100644 --- a/src/native/QmlNet/QmlNet/types/NetTypeManager.h +++ b/src/native/QmlNet/QmlNet/types/NetTypeManager.h @@ -10,6 +10,7 @@ class NetTypeManager { public: NetTypeManager(); static QSharedPointer getTypeInfo(const QString& typeName); + static QSharedPointer getBaseType(QSharedPointer typeInfo); private: static QMap> types; }; diff --git a/src/net/Qml.Net.Sandbox/Program.Tests.cs b/src/net/Qml.Net.Sandbox/Program.Tests.cs index 97205ae4..fcbd4847 100644 --- a/src/net/Qml.Net.Sandbox/Program.Tests.cs +++ b/src/net/Qml.Net.Sandbox/Program.Tests.cs @@ -33,7 +33,6 @@ namespace Qml.Net.Sandbox { public void Dispose() { - } public bool OnMessageWithTypes(IMessageSinkMessage message, HashSet messageTypes) diff --git a/src/net/Qml.Net.Sandbox/Qml.Net.Sandbox.csproj b/src/net/Qml.Net.Sandbox/Qml.Net.Sandbox.csproj index 7470f258..d2c60ae9 100644 --- a/src/net/Qml.Net.Sandbox/Qml.Net.Sandbox.csproj +++ b/src/net/Qml.Net.Sandbox/Qml.Net.Sandbox.csproj @@ -10,12 +10,12 @@ - - - - + + + + \ No newline at end of file diff --git a/src/net/Qml.Net.Tests/Qml/SignalTests.cs b/src/net/Qml.Net.Tests/Qml/SignalTests.cs index de5fff23..9a7b4d3b 100644 --- a/src/net/Qml.Net.Tests/Qml/SignalTests.cs +++ b/src/net/Qml.Net.Tests/Qml/SignalTests.cs @@ -35,7 +35,7 @@ namespace Qml.Net.Tests.Qml public string SomeStringProperty { get => _someStringPropertyValue; - set + set { if (_someStringPropertyValue == value) return; @@ -50,7 +50,7 @@ namespace Qml.Net.Tests.Qml public int SomeIntProperty { get => _someIntPropertyValue; - set + set { if (_someIntPropertyValue == value) return; @@ -63,14 +63,14 @@ namespace Qml.Net.Tests.Qml public bool SomeBoolProperty { - get - { - return _someBoolPropertyValue; - } - - set + get { - if (_someBoolPropertyValue == value) + return _someBoolPropertyValue; + } + + set + { + if (_someBoolPropertyValue == value) return; _someBoolPropertyValue = value; this.ActivateNotifySignal(); @@ -91,6 +91,11 @@ namespace Qml.Net.Tests.Qml { } + [Signal("derivedSignal")] + public class SignalObjectDerived : SignalObject + { + } + [Fact] public void Can_raise_signal_from_qml() { @@ -372,5 +377,97 @@ namespace Qml.Net.Tests.Qml paramResult.Should().NotBeNull(); paramResult.SomeStringProperty.Should().NotBeNull(param.SomeStringProperty); } + + [Fact] + public void Can_raise_base_signal_from_net_on_base_object_from_derived_object() + { + var o = new SignalObjectDerived(); + Mock.Setup(x => x.SignalRaised).Returns(false); + Mock.Setup(x => x.GetSignalObject()).Returns(o); + Mock.Setup(x => x.TestMethod()).Callback(() => + { + o.ActivateSignal("testSignal"); + }); + + RunQmlTest( + "test", + @" + var instance = test.getSignalObject() + instance.testSignal.connect(function() { + test.signalRaised = true + }) + test.testMethod() + "); + Mock.VerifySet(x => x.SignalRaised = true, Times.Once); + } + + [Fact] + public void Can_raise_derived_signal_from_net_on_base_object_from_derived_object() + { + var o = new SignalObjectDerived(); + Mock.Setup(x => x.SignalRaised).Returns(false); + Mock.Setup(x => x.GetSignalObject()).Returns(o); + Mock.Setup(x => x.TestMethod()).Callback(() => + { + o.ActivateSignal("derivedSignal"); + }); + + RunQmlTest( + "test", + @" + var instance = test.getSignalObject() + instance.derivedSignal.connect(function() { + test.signalRaised = true + }) + test.testMethod() + "); + Mock.VerifySet(x => x.SignalRaised = true, Times.Once); + } + + [Fact] + public void Can_raise_base_signal_from_qml_on_base_object_from_derived_object() + { + var o = new SignalObjectDerived(); + Mock.Setup(x => x.SignalRaised).Returns(false); + Mock.Setup(x => x.GetSignalObject()).Returns(o); + Mock.Setup(x => x.TestMethod()).Callback(() => + { + o.ActivateSignal("testSignal"); + }); + + RunQmlTest( + "test", + @" + var instance = test.getSignalObject() + instance.testSignal.connect(function() { + test.signalRaised = true + }) + test.testMethod() + "); + Mock.VerifySet(x => x.SignalRaised = true, Times.Once); + } + + [Fact] + public void Can_raise_derived_signal_from_qml_on_base_object_from_derived_object() + { + var o = new SignalObjectDerived(); + Mock.Setup(x => x.SignalRaised).Returns(false); + Mock.Setup(x => x.GetSignalObject()).Returns(o); + Mock.Setup(x => x.TestMethod()).Callback(() => + { + o.ActivateSignal("derivedSignal"); + }); + + RunQmlTest( + "test", + @" + var instance = test.getSignalObject() + instance.derivedSignal.connect(function() { + test.signalRaised = true + }) + test.testMethod() + "); + Mock.VerifySet(x => x.SignalRaised = true, Times.Once); + } } } \ No newline at end of file diff --git a/src/net/Qml.Net.Tests/Types/NetTypeManagerTests.cs b/src/net/Qml.Net.Tests/Types/NetTypeManagerTests.cs index e3220ba7..2a86b79c 100644 --- a/src/net/Qml.Net.Tests/Types/NetTypeManagerTests.cs +++ b/src/net/Qml.Net.Tests/Types/NetTypeManagerTests.cs @@ -471,5 +471,56 @@ namespace Qml.Net.Tests.Types type.HasObjectDestroyed.Should().BeTrue(); type.HasComponentCompleted.Should().BeFalse(); } + + public class TestType21 + { + public void Method1() + { + } + } + + public class TestType22 : TestType21 + { + public void Method2() + { + } + } + + [Fact] + public void Only_methods_on_class_are_returned() + { + var type1 = NetTypeManager.GetTypeInfo(); + type1.EnsureLoaded(); + type1.MethodCount.Should().Be(1); + type1.GetMethod(0).MethodName.Should().Be("Method1"); + + var type2 = NetTypeManager.GetTypeInfo(); + type2.EnsureLoaded(); + type2.MethodCount.Should().Be(1); + type2.GetMethod(0).MethodName.Should().Be("Method2"); + } + + [Signal("firstSignal")] + public class TestType23 + { + } + + [Signal("secondSignal")] + public class TestType24 : TestType23 + { + } + + [Fact] + public void Can_ignore_inherited_signals() + { + var type1 = NetTypeManager.GetTypeInfo(); + type1.EnsureLoaded(); + type1.SignalCount.Should().Be(1); + type1.GetSignal(0).Name.Should().Be("firstSignal"); + var type2 = NetTypeManager.GetTypeInfo(); + type2.EnsureLoaded(); + type2.SignalCount.Should().Be(1); + type2.GetSignal(0).Name.Should().Be("secondSignal"); + } } } \ No newline at end of file diff --git a/src/net/Qml.Net/Internal/DefaultCallbacks.cs b/src/net/Qml.Net/Internal/DefaultCallbacks.cs index 8f6e86ab..5e58c033 100644 --- a/src/net/Qml.Net/Internal/DefaultCallbacks.cs +++ b/src/net/Qml.Net/Internal/DefaultCallbacks.cs @@ -94,7 +94,7 @@ namespace Qml.Net.Internal type.HasObjectDestroyed = true; } - foreach (var methodInfo in typeInfo.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)) + foreach (var methodInfo in typeInfo.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { if (methodInfo.IsGenericMethod) continue; // No generics supported. if (Helpers.IsPrimitive(methodInfo.DeclaringType)) continue; @@ -121,7 +121,7 @@ namespace Qml.Net.Internal var signals = new Dictionary(); - foreach (var signalAttribute in typeInfo.GetCustomAttributes().OfType()) + foreach (var signalAttribute in typeInfo.GetCustomAttributes(false).OfType()) { if (string.IsNullOrEmpty(signalAttribute.Name)) { diff --git a/src/net/Qml.Net/QCoreApplication.cs b/src/net/Qml.Net/QCoreApplication.cs index 892ba701..c072333c 100644 --- a/src/net/Qml.Net/QCoreApplication.cs +++ b/src/net/Qml.Net/QCoreApplication.cs @@ -256,12 +256,12 @@ namespace Qml.Net public static void SetAttribute(ApplicationAttribute attribute, bool on) { - Interop.QCoreApplication.SetAttribute((int) attribute, on); + Interop.QCoreApplication.SetAttribute((int)attribute, on); } public static bool TestAttribute(ApplicationAttribute attribute) { - return Interop.QCoreApplication.TestAttribute((int) attribute) == 1; + return Interop.QCoreApplication.TestAttribute((int)attribute) == 1; } protected override void DisposeUnmanaged(IntPtr ptr) diff --git a/src/net/styles.ruleset b/src/net/styles.ruleset index d851a79a..0478e187 100644 --- a/src/net/styles.ruleset +++ b/src/net/styles.ruleset @@ -65,6 +65,7 @@ +