qml 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +46 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +15 -0
  5. data/.yardopts +4 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +351 -0
  9. data/Rakefile +6 -0
  10. data/examples/assets/fonts/fontawesome-webfont.ttf +0 -0
  11. data/examples/fizzbuzz/fizzbuzz.rb +43 -0
  12. data/examples/fizzbuzz/main.qml +38 -0
  13. data/examples/imageprovider/imageprovider.rb +57 -0
  14. data/examples/imageprovider/main.qml +51 -0
  15. data/examples/todo/main.qml +70 -0
  16. data/examples/todo/todo.rb +36 -0
  17. data/examples/twitter/main.qml +36 -0
  18. data/examples/twitter/twitter.rb +55 -0
  19. data/ext/qml/accessclass.cpp +71 -0
  20. data/ext/qml/accessclass.h +19 -0
  21. data/ext/qml/accessobject.cpp +30 -0
  22. data/ext/qml/accessobject.h +22 -0
  23. data/ext/qml/application.cpp +54 -0
  24. data/ext/qml/application.h +17 -0
  25. data/ext/qml/common.h +18 -0
  26. data/ext/qml/ext_accesssupport.cpp +77 -0
  27. data/ext/qml/ext_accesssupport.h +42 -0
  28. data/ext/qml/ext_gcmarker.cpp +39 -0
  29. data/ext/qml/ext_gcmarker.h +27 -0
  30. data/ext/qml/ext_kernel.cpp +62 -0
  31. data/ext/qml/ext_kernel.h +11 -0
  32. data/ext/qml/ext_metaobject.cpp +410 -0
  33. data/ext/qml/ext_metaobject.h +62 -0
  34. data/ext/qml/ext_pluginloader.cpp +55 -0
  35. data/ext/qml/ext_pluginloader.h +32 -0
  36. data/ext/qml/ext_pointer.cpp +134 -0
  37. data/ext/qml/ext_pointer.h +43 -0
  38. data/ext/qml/ext_testutil.cpp +42 -0
  39. data/ext/qml/ext_testutil.h +11 -0
  40. data/ext/qml/extconf.rb +84 -0
  41. data/ext/qml/foreignclass.cpp +72 -0
  42. data/ext/qml/foreignclass.h +88 -0
  43. data/ext/qml/foreignmetaobject.cpp +345 -0
  44. data/ext/qml/foreignmetaobject.h +46 -0
  45. data/ext/qml/foreignobject.cpp +22 -0
  46. data/ext/qml/foreignobject.h +21 -0
  47. data/ext/qml/functioninfo.h +16 -0
  48. data/ext/qml/init.cpp +69 -0
  49. data/ext/qml/listmodel.cpp +112 -0
  50. data/ext/qml/listmodel.h +43 -0
  51. data/ext/qml/markable.h +12 -0
  52. data/ext/qml/objectdata.cpp +26 -0
  53. data/ext/qml/objectdata.h +20 -0
  54. data/ext/qml/objectgc.cpp +69 -0
  55. data/ext/qml/objectgc.h +28 -0
  56. data/ext/qml/plugins/core/applicationextension.cpp +34 -0
  57. data/ext/qml/plugins/core/applicationextension.h +28 -0
  58. data/ext/qml/plugins/core/componentextension.cpp +41 -0
  59. data/ext/qml/plugins/core/componentextension.h +28 -0
  60. data/ext/qml/plugins/core/contextextension.cpp +39 -0
  61. data/ext/qml/plugins/core/contextextension.h +29 -0
  62. data/ext/qml/plugins/core/core.pro +29 -0
  63. data/ext/qml/plugins/core/coreplugin.cpp +87 -0
  64. data/ext/qml/plugins/core/coreplugin.h +49 -0
  65. data/ext/qml/plugins/core/engineextension.cpp +27 -0
  66. data/ext/qml/plugins/core/engineextension.h +28 -0
  67. data/ext/qml/plugins/core/imageprovider.cpp +38 -0
  68. data/ext/qml/plugins/core/imageprovider.h +18 -0
  69. data/ext/qml/plugins/core/imagerequestpromise.cpp +19 -0
  70. data/ext/qml/plugins/core/imagerequestpromise.h +21 -0
  71. data/ext/qml/plugins/core/qmlexception.cpp +11 -0
  72. data/ext/qml/plugins/core/qmlexception.h +17 -0
  73. data/ext/qml/plugins/testutil/objectlifechecker.cpp +17 -0
  74. data/ext/qml/plugins/testutil/objectlifechecker.h +24 -0
  75. data/ext/qml/plugins/testutil/ownershiptest.cpp +26 -0
  76. data/ext/qml/plugins/testutil/ownershiptest.h +30 -0
  77. data/ext/qml/plugins/testutil/testobject.cpp +6 -0
  78. data/ext/qml/plugins/testutil/testobject.h +108 -0
  79. data/ext/qml/plugins/testutil/testobjectsubclass.cpp +10 -0
  80. data/ext/qml/plugins/testutil/testobjectsubclass.h +19 -0
  81. data/ext/qml/plugins/testutil/testutil.pro +20 -0
  82. data/ext/qml/plugins/testutil/testutilplugin.cpp +47 -0
  83. data/ext/qml/plugins/testutil/testutilplugin.h +32 -0
  84. data/ext/qml/qmltyperegisterer.cpp +74 -0
  85. data/ext/qml/qmltyperegisterer.h +30 -0
  86. data/ext/qml/rubyclass.cpp +94 -0
  87. data/ext/qml/rubyclass.h +234 -0
  88. data/ext/qml/rubyvalue.cpp +690 -0
  89. data/ext/qml/rubyvalue.h +256 -0
  90. data/ext/qml/signalforwarder.cpp +66 -0
  91. data/ext/qml/signalforwarder.h +29 -0
  92. data/ext/qml/util.cpp +120 -0
  93. data/ext/qml/util.h +101 -0
  94. data/ext/qml/valuereference.cpp +50 -0
  95. data/ext/qml/valuereference.h +22 -0
  96. data/ext/qml/weakvaluereference.cpp +27 -0
  97. data/ext/qml/weakvaluereference.h +19 -0
  98. data/lib/qml.rb +41 -0
  99. data/lib/qml/access.rb +137 -0
  100. data/lib/qml/application.rb +139 -0
  101. data/lib/qml/class_builder.rb +126 -0
  102. data/lib/qml/component.rb +53 -0
  103. data/lib/qml/context.rb +71 -0
  104. data/lib/qml/data.rb +2 -0
  105. data/lib/qml/data/array_model.rb +103 -0
  106. data/lib/qml/data/error.rb +5 -0
  107. data/lib/qml/data/list_model.rb +146 -0
  108. data/lib/qml/dispatchable.rb +34 -0
  109. data/lib/qml/dispatcher.rb +61 -0
  110. data/lib/qml/engine.rb +54 -0
  111. data/lib/qml/error_converter.rb +15 -0
  112. data/lib/qml/errors.rb +26 -0
  113. data/lib/qml/geometry.rb +3 -0
  114. data/lib/qml/geometry/point.rb +5 -0
  115. data/lib/qml/geometry/rectangle.rb +5 -0
  116. data/lib/qml/geometry/size.rb +5 -0
  117. data/lib/qml/image_provider.rb +87 -0
  118. data/lib/qml/meta_object.rb +20 -0
  119. data/lib/qml/models.rb +1 -0
  120. data/lib/qml/name_helper.rb +12 -0
  121. data/lib/qml/platform.rb +15 -0
  122. data/lib/qml/plugin_loader.rb +46 -0
  123. data/lib/qml/plugins.rb +26 -0
  124. data/lib/qml/qml.rb +1 -0
  125. data/lib/qml/qt.rb +6 -0
  126. data/lib/qml/qt_classes.rb +9 -0
  127. data/lib/qml/qt_object_base.rb +108 -0
  128. data/lib/qml/reactive.rb +8 -0
  129. data/lib/qml/reactive/bindable.rb +79 -0
  130. data/lib/qml/reactive/chained_signal.rb +25 -0
  131. data/lib/qml/reactive/error.rb +5 -0
  132. data/lib/qml/reactive/object.rb +278 -0
  133. data/lib/qml/reactive/property.rb +19 -0
  134. data/lib/qml/reactive/signal.rb +116 -0
  135. data/lib/qml/reactive/signal_spy.rb +27 -0
  136. data/lib/qml/reactive/signals/map_signal.rb +21 -0
  137. data/lib/qml/reactive/signals/merge_signal.rb +21 -0
  138. data/lib/qml/reactive/signals/select_signal.rb +21 -0
  139. data/lib/qml/reactive/simple_property.rb +17 -0
  140. data/lib/qml/reactive/unbound_property.rb +42 -0
  141. data/lib/qml/reactive/unbound_signal.rb +51 -0
  142. data/lib/qml/root_path.rb +3 -0
  143. data/lib/qml/test_util.rb +1 -0
  144. data/lib/qml/test_util/object_life_checker.rb +17 -0
  145. data/lib/qml/version.rb +3 -0
  146. data/lib/qml/wrappable.rb +9 -0
  147. data/qml.gemspec +28 -0
  148. data/spec/assets/testobj.qml +5 -0
  149. data/spec/qml/.access_spec.rb.swp +0 -0
  150. data/spec/qml/access_spec.rb +162 -0
  151. data/spec/qml/application_spec.rb +43 -0
  152. data/spec/qml/component_spec.rb +44 -0
  153. data/spec/qml/context_spec.rb +43 -0
  154. data/spec/qml/conversion_spec.rb +59 -0
  155. data/spec/qml/data/array_model_spec.rb +215 -0
  156. data/spec/qml/dispatchable_spec.rb +26 -0
  157. data/spec/qml/dispatcher_spec.rb +48 -0
  158. data/spec/qml/geometry/point_spec.rb +4 -0
  159. data/spec/qml/geometry/rectangle_spec.rb +4 -0
  160. data/spec/qml/geometry/size_spec.rb +4 -0
  161. data/spec/qml/plugin_loader_spec.rb +33 -0
  162. data/spec/qml/qt_object_base_spec.rb +119 -0
  163. data/spec/qml/reactive/object_spec.rb +273 -0
  164. data/spec/qml/reactive/property_spec.rb +70 -0
  165. data/spec/qml/reactive/signal_spec.rb +191 -0
  166. data/spec/qml/reactive/signal_spy_spec.rb +26 -0
  167. data/spec/qml/test_object_spec.rb +186 -0
  168. data/spec/qml_spec.rb +7 -0
  169. data/spec/spec_helper.rb +5 -0
  170. metadata +321 -0
@@ -0,0 +1,19 @@
1
+ #pragma once
2
+ #include "foreignclass.h"
3
+ #include "rubyvalue.h"
4
+
5
+ namespace RubyQml {
6
+
7
+ class AccessWrapper;
8
+
9
+ class AccessClass : public ForeignClass
10
+ {
11
+ public:
12
+ AccessClass(RubyValue className, RubyValue methodInfos, RubyValue signalInfos, RubyValue propertyInfos);
13
+
14
+ QVariant callMethod(ForeignObject *obj, size_t id, const QVariantList &args) override;
15
+ void setProperty(ForeignObject *obj, size_t id, const QVariant &value) override;
16
+ QVariant getProperty(ForeignObject *obj, size_t id) override;
17
+ };
18
+
19
+ } // namespace RubyQml
@@ -0,0 +1,30 @@
1
+ #include "accessobject.h"
2
+ #include "util.h"
3
+ #include "rubyclass.h"
4
+ #include <QSet>
5
+
6
+ namespace RubyQml {
7
+
8
+ AccessWrapper::AccessWrapper(const SP<ForeignMetaObject> &metaobj, RubyValue value) :
9
+ ForeignObject(metaobj),
10
+ mWrapped(value)
11
+ {
12
+ if (!value.isKindOf(RubyModule::fromPath("QML::Access"))) {
13
+ std::logic_error("wrapping non QML::Access object");
14
+ }
15
+ value.send("access_wrappers").send("push", RubyValue::fromQObject(this, false));
16
+ }
17
+
18
+ AccessWrapper::~AccessWrapper()
19
+ {
20
+ if (mWrapped.hasValue()) {
21
+ mWrapped.value().send("access_wrappers").send("delete", RubyValue::fromQObject(this, false));
22
+ }
23
+ }
24
+
25
+ void AccessWrapper::gc_mark()
26
+ {
27
+ rb_gc_mark(mWrapped.value());
28
+ }
29
+
30
+ } // namespace RubyQml
@@ -0,0 +1,22 @@
1
+ #pragma once
2
+ #include "weakvaluereference.h"
3
+ #include "foreignobject.h"
4
+ #include "markable.h"
5
+
6
+ namespace RubyQml {
7
+
8
+ class AccessWrapper : public ForeignObject, public Markable
9
+ {
10
+ public:
11
+ AccessWrapper(const SP<ForeignMetaObject> &metaobj, RubyValue wrappedValue);
12
+ ~AccessWrapper();
13
+
14
+ RubyValue wrappedValue() { return mWrapped.value(); }
15
+ void gc_mark() override;
16
+
17
+ private:
18
+ WeakValueReference mWrapped;
19
+ };
20
+
21
+ } // namespace RubyQml
22
+
@@ -0,0 +1,54 @@
1
+ #include "application.h"
2
+ #include "util.h"
3
+
4
+ namespace RubyQml {
5
+ namespace Application {
6
+
7
+ namespace {
8
+
9
+ int argc;
10
+ QList<QByteArray> argData;
11
+ char **argv;
12
+
13
+ QApplication *application_;
14
+ QQmlEngine *engine_;
15
+
16
+ }
17
+
18
+ void failIfUninitialized()
19
+ {
20
+ if (!initialized()) {
21
+ fail("QML::UninitializedError", "ruby-qml not yet initialized");
22
+ }
23
+ }
24
+
25
+ QApplication *application()
26
+ {
27
+ failIfUninitialized();
28
+ return application_;
29
+ }
30
+
31
+ QQmlEngine *engine()
32
+ {
33
+ failIfUninitialized();
34
+ return engine_;
35
+ }
36
+
37
+ bool initialized()
38
+ {
39
+ return application_;
40
+ }
41
+
42
+ void init(const QList<QByteArray> &args)
43
+ {
44
+ argc = args.size();
45
+ argData = args;
46
+ argv = new char*[argc];
47
+ std::transform(argData.begin(), argData.end(), argv, [](QByteArray &ba) { return ba.data(); });
48
+
49
+ application_ = new QApplication(argc, argv);
50
+ engine_ = new QQmlEngine();
51
+ }
52
+
53
+ }
54
+ } // namespace RubyQml
@@ -0,0 +1,17 @@
1
+ #pragma once
2
+
3
+ #include <QList>
4
+ #include <QApplication>
5
+ #include <QQmlEngine>
6
+
7
+ namespace RubyQml {
8
+ namespace Application {
9
+
10
+ QApplication *application();
11
+ QQmlEngine *engine();
12
+ bool initialized();
13
+
14
+ void init(const QList<QByteArray> &args);
15
+
16
+ }
17
+ } // namespace RubyQml
@@ -0,0 +1,18 @@
1
+ #pragma once
2
+ #include <memory>
3
+
4
+ namespace RubyQml {
5
+
6
+ template <typename T>
7
+ using SP = std::shared_ptr<T>;
8
+
9
+ template <typename T>
10
+ using WP = std::weak_ptr<T>;
11
+
12
+ template <typename T, typename ... Args>
13
+ inline SP<T> makeSP(Args && ... args)
14
+ {
15
+ return std::make_shared<T>(std::forward<Args>(args)...);
16
+ }
17
+
18
+ } // namespace RubyQml
@@ -0,0 +1,77 @@
1
+ #include "ext_accesssupport.h"
2
+ #include "ext_pointer.h"
3
+ #include "rubyclass.h"
4
+ #include "accessclass.h"
5
+ #include "accessobject.h"
6
+ #include "foreignmetaobject.h"
7
+ #include "qmltyperegisterer.h"
8
+
9
+ namespace RubyQml {
10
+ namespace Ext {
11
+
12
+ AccessWrapperFactory::AccessWrapperFactory(RubyValue self) :
13
+ self(self)
14
+ {
15
+ }
16
+
17
+ AccessWrapperFactory::~AccessWrapperFactory()
18
+ {
19
+ }
20
+
21
+ RubyValue AccessWrapperFactory::ext_initialize(RubyValue rubyClass, RubyValue className, RubyValue methodInfos, RubyValue signalInfos, RubyValue propertyInfos)
22
+ {
23
+ mRubyClass = rubyClass;
24
+ mAccessClass = makeSP<AccessClass>(className, methodInfos, signalInfos, propertyInfos);
25
+ mMetaObject = makeSP<ForeignMetaObject>(mAccessClass);
26
+ return self;
27
+ }
28
+
29
+ RubyValue AccessWrapperFactory::ext_emitSignal(RubyValue obj, RubyValue name, RubyValue args)
30
+ {
31
+ auto accessObj = wrapperRubyClass<Pointer>().unwrap(obj.send("access_object"))->fetchQObject();
32
+ auto nameId = name.toID();
33
+ auto argVariants = args.to<QVariantList>();
34
+ withoutGvl([&] {
35
+ mMetaObject->emitSignal(dynamic_cast<ForeignObject *>(accessObj), nameId, argVariants);
36
+ });
37
+ return Qnil;
38
+ }
39
+
40
+ RubyValue AccessWrapperFactory::ext_registerToQml(RubyValue path, RubyValue majorVersion, RubyValue minorVersion, RubyValue name)
41
+ {
42
+ using namespace std::placeholders;
43
+ if (!mTypeRegisterer) {
44
+ mTypeRegisterer = makeSP<QmlTypeRegisterer>(mMetaObject, std::bind(&AccessWrapperFactory::newInstanceInto, this, _1));
45
+ mTypeRegisterer->registerType(path.to<QByteArray>(), majorVersion.to<int>(), minorVersion.to<int>(), name.to<QByteArray>());
46
+ }
47
+ return self;
48
+ }
49
+
50
+ RubyValue AccessWrapperFactory::ext_create(RubyValue access)
51
+ {
52
+ return RubyValue::fromQObject(create(access), false);
53
+ }
54
+
55
+ AccessWrapper *AccessWrapperFactory::create(RubyValue access)
56
+ {
57
+ return new AccessWrapper(mMetaObject, access);
58
+ }
59
+
60
+ void AccessWrapperFactory::newInstanceInto(void *where)
61
+ {
62
+ withGvl([&] {
63
+ new(where) AccessWrapper(mMetaObject, mRubyClass.send("new"));
64
+ });
65
+ }
66
+
67
+ void AccessWrapperFactory::defineClass()
68
+ {
69
+ WrapperRubyClass<AccessWrapperFactory> klass(RubyModule::fromPath("QML"), "AccessWrapperFactory");
70
+ klass.defineMethod(MethodAccess::Protected, "initialize", RUBYQML_MEMBER_FUNCTION_INFO(&AccessWrapperFactory::ext_initialize));
71
+ klass.defineMethod("emit_signal", RUBYQML_MEMBER_FUNCTION_INFO(&AccessWrapperFactory::ext_emitSignal));
72
+ klass.defineMethod("register_to_qml", RUBYQML_MEMBER_FUNCTION_INFO(&AccessWrapperFactory::ext_registerToQml));
73
+ klass.defineMethod("create", RUBYQML_MEMBER_FUNCTION_INFO(&AccessWrapperFactory::ext_create));
74
+ }
75
+
76
+ } // namespace Ext
77
+ } // namespace RubyQml
@@ -0,0 +1,42 @@
1
+ #pragma once
2
+ #include "common.h"
3
+ #include "rubyvalue.h"
4
+
5
+ namespace RubyQml {
6
+
7
+ class AccessClass;
8
+ class ForeignMetaObject;
9
+ class QmlTypeRegisterer;
10
+ class AccessWrapper;
11
+
12
+ namespace Ext {
13
+
14
+ class AccessWrapperFactory
15
+ {
16
+ public:
17
+ AccessWrapperFactory(RubyValue self);
18
+ ~AccessWrapperFactory();
19
+
20
+ RubyValue ext_initialize(RubyValue rubyClass, RubyValue className, RubyValue methodInfos, RubyValue signalInfos, RubyValue propertyInfos);
21
+ RubyValue ext_emitSignal(RubyValue obj, RubyValue name, RubyValue args);
22
+ RubyValue ext_registerToQml(RubyValue path, RubyValue majorVersion, RubyValue minorVersion, RubyValue name);
23
+ RubyValue ext_create(RubyValue access);
24
+
25
+ AccessWrapper *create(RubyValue access);
26
+
27
+ void gc_mark() {}
28
+ static void defineClass();
29
+
30
+ private:
31
+
32
+ void newInstanceInto(void *where);
33
+
34
+ const RubyValue self;
35
+ RubyValue mRubyClass;
36
+ SP<AccessClass> mAccessClass;
37
+ SP<ForeignMetaObject> mMetaObject;
38
+ SP<QmlTypeRegisterer> mTypeRegisterer;
39
+ };
40
+
41
+ } // namespace Ext
42
+ } // namespace RubyQml
@@ -0,0 +1,39 @@
1
+ #include "ext_gcmarker.h"
2
+ #include "rubyclass.h"
3
+ #include <QtCore/QSet>
4
+
5
+ namespace RubyQml {
6
+ namespace Ext {
7
+
8
+ GCMarker::GCMarker(RubyValue self)
9
+ {
10
+ Q_UNUSED(self)
11
+ }
12
+
13
+ RubyValue GCMarker::fromMarkFunction(const std::function<void ()> &func)
14
+ {
15
+ auto klass = wrapperRubyClass<GCMarker>();
16
+ auto marker = klass.newInstance();
17
+ klass.unwrap(marker)->setMarkFunction(func);
18
+ return marker;
19
+ }
20
+
21
+ void GCMarker::setMarkFunction(const std::function<void ()> &func)
22
+ {
23
+ mMarkFunc = func;
24
+ }
25
+
26
+ void GCMarker::gc_mark()
27
+ {
28
+ if (mMarkFunc)
29
+ mMarkFunc();
30
+ }
31
+
32
+ void GCMarker::defineClass()
33
+ {
34
+ WrapperRubyClass<GCMarker> klass(RubyModule::fromPath("QML"), "GCProtection");
35
+ Q_UNUSED(klass);
36
+ }
37
+
38
+ } // namespace Ext
39
+ } // namespace RubyQml
@@ -0,0 +1,27 @@
1
+ #pragma once
2
+
3
+ #include "rubyvalue.h"
4
+ #include <functional>
5
+
6
+ namespace RubyQml {
7
+
8
+ namespace Ext {
9
+
10
+ class GCMarker
11
+ {
12
+ public:
13
+ GCMarker(RubyValue self);
14
+ static RubyValue fromMarkFunction(const std::function<void ()> &func);
15
+
16
+ void setMarkFunction(const std::function<void()> &func);
17
+ void gc_mark();
18
+
19
+ static void defineClass();
20
+
21
+ private:
22
+ std::function<void ()> mMarkFunc;
23
+ };
24
+
25
+ } // namespace Ext
26
+
27
+ } // namespace RubyQml
@@ -0,0 +1,62 @@
1
+ #include "ext_kernel.h"
2
+ #include "application.h"
3
+ #include "rubyvalue.h"
4
+ #include "rubyclass.h"
5
+
6
+ namespace RubyQml {
7
+ namespace Ext {
8
+ namespace Kernel {
9
+
10
+ namespace {
11
+
12
+ RubyValue application(RubyValue self)
13
+ {
14
+ Q_UNUSED(self);
15
+ return RubyValue::from(Application::application());
16
+ }
17
+
18
+ RubyValue engine(RubyValue self)
19
+ {
20
+ Q_UNUSED(self);
21
+ return RubyValue::from(Application::engine());
22
+ }
23
+
24
+ RubyValue applicationMetaObject()
25
+ {
26
+ return RubyValue::from(&QApplication::staticMetaObject);
27
+ }
28
+
29
+ RubyValue engineMetaObject()
30
+ {
31
+ return RubyValue::from(&QQmlEngine::staticMetaObject);
32
+ }
33
+
34
+ RubyValue init(RubyValue self, RubyValue argv)
35
+ {
36
+ Q_UNUSED(self);
37
+ Application::init(argv.to<QList<QByteArray>>());
38
+ return Qnil;
39
+ }
40
+
41
+ RubyValue initialized(RubyValue self)
42
+ {
43
+ Q_UNUSED(self);
44
+ return RubyValue::from(Application::initialized());
45
+ }
46
+
47
+ }
48
+
49
+ void defineModule()
50
+ {
51
+ RubyModule kernel(RubyModule::fromPath("QML"), "Kernel");
52
+ kernel.defineModuleFunction("application", RUBYQML_FUNCTION_INFO(&application));
53
+ kernel.defineModuleFunction("engine", RUBYQML_FUNCTION_INFO(&engine));
54
+ kernel.defineModuleFunction("application_meta_object", RUBYQML_FUNCTION_INFO(&applicationMetaObject));
55
+ kernel.defineModuleFunction("engine_meta_object", RUBYQML_FUNCTION_INFO(&engineMetaObject));
56
+ kernel.defineModuleFunction("init", RUBYQML_FUNCTION_INFO(&init));
57
+ kernel.defineModuleFunction("initialized?", RUBYQML_FUNCTION_INFO(&initialized));
58
+ }
59
+
60
+ } // namespace Kernel
61
+ } // namespace Ext
62
+ } // namespace RubyQml
@@ -0,0 +1,11 @@
1
+ #pragma once
2
+
3
+ namespace RubyQml {
4
+ namespace Ext {
5
+ namespace Kernel {
6
+
7
+ void defineModule();
8
+
9
+ } // namespace Kernel
10
+ } // namespace Ext
11
+ } // namespace RubyQml
@@ -0,0 +1,410 @@
1
+ #include "ext_metaobject.h"
2
+ #include "util.h"
3
+ #include "ext_pointer.h"
4
+ #include "rubyclass.h"
5
+ #include "signalforwarder.h"
6
+ #include <QtCore/QMetaObject>
7
+ #include <QtCore/QMetaMethod>
8
+ #include <QtCore/QMetaProperty>
9
+ #include <QtCore/QMetaEnum>
10
+ #include <QtCore/QVariant>
11
+ #include <array>
12
+ #include <QtCore/QDebug>
13
+ #include <QtQml/QQmlEngine>
14
+
15
+ namespace RubyQml {
16
+ namespace Ext {
17
+
18
+ namespace {
19
+
20
+ RubyValue idListToArray(const QList<ID> &xs)
21
+ {
22
+ return protect([&] {
23
+ auto ary = rb_ary_new();
24
+ for (ID id : xs) {
25
+ rb_ary_push(ary, ID2SYM(id));
26
+ }
27
+ return ary;
28
+ });
29
+ }
30
+
31
+ }
32
+
33
+ MetaObject::MetaObject(RubyValue self) :
34
+ self(self)
35
+ {
36
+ setMetaObject(&QObject::staticMetaObject);
37
+ }
38
+
39
+ RubyValue MetaObject::className() const
40
+ {
41
+ return rb_str_new_cstr(mMetaObject->className());
42
+ }
43
+
44
+ RubyValue MetaObject::methodNames() const
45
+ {
46
+ return idListToArray(mMethodHash.keys());
47
+ }
48
+
49
+ RubyValue MetaObject::isPublic(RubyValue name) const
50
+ {
51
+ auto methods = findMethods(name);
52
+ return RubyValue::from(mMetaObject->method(methods.first()).access() == QMetaMethod::Public);
53
+ }
54
+ RubyValue MetaObject::isProtected(RubyValue name) const
55
+ {
56
+ auto methods = findMethods(name);
57
+ return RubyValue::from(mMetaObject->method(methods.first()).access() == QMetaMethod::Protected);
58
+ }
59
+ RubyValue MetaObject::isPrivate(RubyValue name) const
60
+ {
61
+ auto methods = findMethods(name);
62
+ return RubyValue::from(mMetaObject->method(methods.first()).access() == QMetaMethod::Private);
63
+ }
64
+ RubyValue MetaObject::isSignal(RubyValue name) const
65
+ {
66
+ auto methods = findMethods(name);
67
+ return RubyValue::from(mMetaObject->method(methods.first()).methodType() == QMetaMethod::Signal);
68
+ }
69
+
70
+ class MethodInvoker
71
+ {
72
+ public:
73
+ MethodInvoker(RubyValue args, const QMetaMethod &method) :
74
+ mArgs(args), mMethod(method) {}
75
+
76
+ bool isArgsCompatible() const
77
+ {
78
+ int count = RARRAY_LEN(VALUE(mArgs));
79
+ if (mMethod.parameterCount() != count) {
80
+ return false;
81
+ }
82
+ for (int i = 0; i < count; ++i) {
83
+ auto metaType = mMethod.parameterType(i);
84
+ RubyValue arg = RARRAY_AREF(VALUE(mArgs), i);
85
+ if (!arg.isConvertibleTo(metaType)) {
86
+ return false;
87
+ }
88
+ }
89
+ return true;
90
+ }
91
+
92
+ RubyValue invoke(QObject *obj)
93
+ {
94
+ std::array<QVariant, 10> argVariants;
95
+ std::array<QGenericArgument, 10> args;
96
+ for (int i = 0; i < mMethod.parameterCount(); ++i) {
97
+ auto metaType = mMethod.parameterType(i);
98
+ argVariants[i] = RubyValue(RARRAY_AREF(VALUE(mArgs), i)).toVariant(metaType);
99
+ args[i] = QGenericArgument(QMetaType::typeName(metaType), argVariants[i].data());
100
+ }
101
+
102
+ int returnType = mMethod.returnType();
103
+ if (returnType == QMetaType::UnknownType) {
104
+ fail("QML::MethodError", "unknown return metatype");
105
+ }
106
+ bool voidReturning = (returnType == QMetaType::Void);
107
+ QVariant returnValue;
108
+ if (!voidReturning) {
109
+ returnValue = QVariant(returnType, QMetaType::create(returnType));
110
+ }
111
+
112
+ bool result;
113
+ withoutGvl([&] {
114
+ result = mMethod.invoke(
115
+ obj,
116
+ Qt::DirectConnection,
117
+ QGenericReturnArgument(QMetaType::typeName(returnType), returnValue.data()),
118
+ args[0],args[1],args[2],args[3],args[4],
119
+ args[5],args[6],args[7],args[8],args[9]);
120
+ });
121
+
122
+ if (!result) {
123
+ QString error;
124
+ QDebug(&error) << "failed to call method" << mMethod.methodSignature();
125
+ fail("QML::MethodError", error);
126
+ }
127
+ if (voidReturning) {
128
+ return Qnil;
129
+ } else {
130
+ auto ret = RubyValue::from(returnValue);
131
+ static auto objectBaseClass = RubyClass::fromPath("QML::QtObjectBase");
132
+ if (ret.isKindOf(objectBaseClass)) {
133
+ auto pointer = wrapperRubyClass<Pointer>().unwrap(ret.send("pointer"));
134
+ pointer->preferManaged(true);
135
+ }
136
+ return ret;
137
+ }
138
+ }
139
+ private:
140
+ RubyValue mArgs;
141
+ QMetaMethod mMethod;
142
+ };
143
+
144
+ RubyValue MetaObject::invokeMethod(RubyValue object, RubyValue methodName, RubyValue args) const
145
+ {
146
+ checkThread();
147
+
148
+ auto methodIndexes = findMethods(methodName);
149
+
150
+ protect([&] {
151
+ args = rb_check_array_type(args);
152
+ });
153
+ auto obj = wrapperRubyClass<Pointer>().unwrap(object)->fetchQObject();
154
+ for (int i : methodIndexes) {
155
+ MethodInvoker invoker(args, mMetaObject->method(i));
156
+ if (invoker.isArgsCompatible()) {
157
+ return invoker.invoke(obj);
158
+ }
159
+ }
160
+ protect([&] {
161
+ auto to_class = rb_funcall(ID2SYM(rb_intern("class")), rb_intern("to_proc"), 0);
162
+ auto classes = rb_funcall_with_block(args, rb_intern("map"), 0, nullptr, to_class);
163
+ auto classes_str = rb_funcall(classes, rb_intern("to_s"), 0);
164
+
165
+ rb_raise(rb_path2class("QML::MethodError"),
166
+ "method mismatch (%s with params %s in %s)",
167
+ mMetaObject->method(methodIndexes.first()).name().data(),
168
+ StringValueCStr(classes_str),
169
+ mMetaObject->className());
170
+ });
171
+ return Qnil;
172
+ }
173
+
174
+ RubyValue MetaObject::connectSignal(RubyValue object, RubyValue signalName, RubyValue proc) const
175
+ {
176
+ auto id = signalName.toID();
177
+ auto obj = wrapperRubyClass<Pointer>().unwrap(object)->fetchQObject();
178
+
179
+ proc = proc.send("to_proc");
180
+
181
+ auto methodIndexes = mMethodHash.values(id);
182
+ std::reverse(methodIndexes.begin(), methodIndexes.end());
183
+
184
+ for (int i : methodIndexes) {
185
+ auto method = mMetaObject->method(i);
186
+ if (method.methodType() != QMetaMethod::Signal) {
187
+ continue;
188
+ }
189
+ new SignalForwarder(obj, method, proc);
190
+ return Qnil;
191
+ }
192
+ protect([&] {
193
+ rb_raise(rb_path2class("QML::MethodError"),
194
+ "signal not found (%s in %s)",
195
+ rb_id2name(id), mMetaObject->className());
196
+ });
197
+ return Qnil;
198
+ }
199
+
200
+ RubyValue MetaObject::propertyNames() const
201
+ {
202
+ return idListToArray(mPropertyHash.keys());
203
+ }
204
+
205
+ RubyValue MetaObject::getProperty(RubyValue object, RubyValue name) const
206
+ {
207
+ checkThread();
208
+
209
+ auto metaProperty = mMetaObject->property(findProperty(name));
210
+
211
+ auto qobj = wrapperRubyClass<Pointer>().unwrap(object)->fetchQObject();
212
+ QVariant result;
213
+ withoutGvl([&] {
214
+ result = metaProperty.read(qobj);
215
+ });
216
+ return RubyValue::from(result);
217
+ }
218
+
219
+ RubyValue MetaObject::setProperty(RubyValue object, RubyValue name, RubyValue newValue) const
220
+ {
221
+ checkThread();
222
+
223
+ auto metaProperty = mMetaObject->property(findProperty(name));
224
+ if (!newValue.isConvertibleTo(metaProperty.userType())) {
225
+ protect([&] {
226
+ rb_raise(rb_path2class("QML::PropertyError"),
227
+ "type mismatch (%s for %s)",
228
+ rb_obj_classname(newValue), metaProperty.typeName());
229
+ });
230
+ }
231
+
232
+ auto qobj = wrapperRubyClass<Pointer>().unwrap(object)->fetchQObject();
233
+ auto variant = newValue.to<QVariant>();
234
+ QVariant result;
235
+ withoutGvl([&] {
236
+ metaProperty.write(qobj, variant);
237
+ result = metaProperty.read(qobj);
238
+ });
239
+ return RubyValue::from(result);
240
+ }
241
+
242
+ RubyValue MetaObject::notifySignal(RubyValue name) const
243
+ {
244
+ auto metaProperty = mMetaObject->property(findProperty(name));
245
+ auto signal = metaProperty.notifySignal();
246
+ if (signal.isValid()) {
247
+ return ID2SYM(rb_intern(metaProperty.notifySignal().name()));
248
+ }
249
+ else {
250
+ return Qnil;
251
+ }
252
+ }
253
+
254
+ void MetaObject::checkThread() const
255
+ {
256
+ if (rb_thread_current() != rb_thread_main()) {
257
+ fail("QML::InvalidThreadError", "Qt object accessed from non-main thread");
258
+ }
259
+ }
260
+
261
+ QList<int> MetaObject::findMethods(RubyValue name) const
262
+ {
263
+ auto id = name.toID();
264
+ auto methodIndexes = mMethodHash.values(id);
265
+ if (methodIndexes.size() == 0) {
266
+ protect([&] {
267
+ rb_raise(rb_path2class("QML::MethodError"),
268
+ "method not found (%s in %s)",
269
+ rb_id2name(id),
270
+ mMetaObject->className());
271
+ });
272
+ }
273
+ return methodIndexes;
274
+ }
275
+
276
+ int MetaObject::findProperty(RubyValue name) const
277
+ {
278
+ auto id = name.toID();
279
+ if (!mPropertyHash.contains(id)) {
280
+ protect([&] {
281
+ rb_raise(rb_path2class("QML::PropertyError"),
282
+ "property not found (%s in %s)",
283
+ rb_id2name(id), mMetaObject->className());
284
+ });
285
+ }
286
+ return mPropertyHash[id];
287
+ }
288
+
289
+ RubyValue MetaObject::enumerators() const
290
+ {
291
+ QHash<QByteArray, QHash<QByteArray, int>> enums;
292
+
293
+ for (int i = mMetaObject->enumeratorOffset(); i < mMetaObject->enumeratorCount(); ++i) {
294
+ auto metaEnum = mMetaObject->enumerator(i);
295
+ if (metaEnum.isFlag()) {
296
+ continue;
297
+ }
298
+
299
+ QHash<QByteArray, int> enumHash;
300
+ for (int j = 0; j < metaEnum.keyCount(); ++j) {
301
+ enumHash[metaEnum.key(j)] = metaEnum.value(j);
302
+ }
303
+ enums[metaEnum.name()] = enumHash;
304
+ }
305
+
306
+ return RubyValue::from(enums);
307
+ }
308
+
309
+ RubyValue MetaObject::superClass() const
310
+ {
311
+ auto superclass = mMetaObject->superClass();
312
+ if (!superclass) {
313
+ return Qnil;
314
+ }
315
+ return fromMetaObject(superclass);
316
+ }
317
+
318
+ RubyValue MetaObject::isEqual(RubyValue other) const
319
+ {
320
+ return RubyValue::from(mMetaObject == wrapperRubyClass<MetaObject>().unwrap(other)->mMetaObject);
321
+ }
322
+
323
+ RubyValue MetaObject::hash() const
324
+ {
325
+ return RubyValue::from(reinterpret_cast<size_t>(mMetaObject)).send("hash");
326
+ }
327
+
328
+ void MetaObject::setMetaObject(const QMetaObject *metaObject)
329
+ {
330
+ int methodCount = metaObject->methodCount() - metaObject->methodOffset();
331
+
332
+ QMultiHash<ID, int> methodHash;
333
+ QHash<ID, int> propertyHash;
334
+
335
+ for (int i = 0; i < methodCount; ++i) {
336
+
337
+ auto index = i + metaObject->methodOffset();
338
+ auto method = metaObject->method(index);
339
+
340
+ if (method.methodType() == QMetaMethod::Constructor) {
341
+ continue;
342
+ }
343
+
344
+ methodHash.insert(rb_intern(method.name()), index);
345
+ }
346
+
347
+ int propertyCount = metaObject->propertyCount() - metaObject->propertyOffset();
348
+
349
+ for (int i = 0; i < propertyCount; ++i) {
350
+ auto index = i + metaObject->propertyOffset();
351
+ auto property = metaObject->property(index);
352
+ propertyHash[rb_intern(property.name())] = index;
353
+ }
354
+
355
+ int enumCount = metaObject->enumeratorCount() - metaObject->enumeratorOffset();
356
+ for (int i = 0; i < enumCount; ++i) {
357
+ auto index = i + metaObject->enumeratorOffset();
358
+ auto metaEnum = metaObject->enumerator(index);
359
+ auto typeName = QByteArray(metaObject->className()) + "::" + QByteArray(metaEnum.name());
360
+ auto metaType = QMetaType::type(typeName);
361
+ if (metaType != QMetaType::UnknownType) {
362
+ RubyValue::addEnumeratorMetaType(metaType);
363
+ }
364
+ }
365
+
366
+ mMetaObject = metaObject;
367
+ mMethodHash = methodHash;
368
+ mPropertyHash = propertyHash;
369
+ }
370
+
371
+ RubyValue MetaObject::fromMetaObject(const QMetaObject *metaObject)
372
+ {
373
+ auto klass = wrapperRubyClass<MetaObject>();
374
+ auto value = klass.newInstance();
375
+ klass.unwrap(value)->setMetaObject(metaObject);
376
+ return value;
377
+ }
378
+
379
+ void MetaObject::defineClass()
380
+ {
381
+ WrapperRubyClass<MetaObject> klass(RubyModule::fromPath("QML"), "MetaObject");
382
+
383
+ klass.defineMethod("name", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::className));
384
+
385
+ klass.defineMethod("method_names", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::methodNames));
386
+ klass.defineMethod("public?", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::isPublic));
387
+ klass.defineMethod("protected?", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::isProtected));
388
+ klass.defineMethod("private?", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::isPrivate));
389
+ klass.defineMethod("signal?", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::isSignal));
390
+
391
+ klass.defineMethod("invoke_method", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::invokeMethod));
392
+ klass.defineMethod("connect_signal", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::connectSignal));
393
+
394
+ klass.defineMethod("property_names", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::propertyNames));
395
+ klass.defineMethod("get_property", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::getProperty));
396
+ klass.defineMethod("set_property", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::setProperty));
397
+ klass.defineMethod("notify_signal", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::notifySignal));
398
+
399
+ klass.defineMethod("enumerators", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::enumerators));
400
+
401
+ klass.defineMethod("super_class", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::superClass));
402
+
403
+ klass.defineMethod("==", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::isEqual));
404
+ klass.defineMethod("hash", RUBYQML_MEMBER_FUNCTION_INFO(&MetaObject::hash));
405
+
406
+ klass.aliasMethod("eql?", "==");
407
+ }
408
+
409
+ } // namespace Ext
410
+ } // namespace RubyQml