qml 0.0.5 → 0.0.6

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -4
  3. data/changes.md +8 -0
  4. data/examples/fizzbuzz/fizzbuzz.rb +1 -1
  5. data/examples/imageprovider/imageprovider.rb +1 -1
  6. data/examples/todo_array/todo_array.rb +1 -1
  7. data/examples/todo_sequel/todo_sequel.rb +1 -1
  8. data/examples/twitter/twitter.rb +1 -1
  9. data/ext/qml/accessclass.cpp +5 -9
  10. data/ext/qml/conversionerror.h +14 -0
  11. data/ext/qml/ext_metaobject.cpp +30 -41
  12. data/ext/qml/init.cpp +5 -7
  13. data/ext/qml/rubyclass.cpp +6 -18
  14. data/ext/qml/rubyclass.h +22 -26
  15. data/ext/qml/rubyvalue.cpp +155 -221
  16. data/ext/qml/rubyvalue.h +30 -63
  17. data/ext/qml/signalforwarder.cpp +1 -3
  18. data/ext/qml/util.cpp +2 -25
  19. data/ext/qml/util.h +1 -21
  20. data/ext/qml/weakvaluereference.cpp +4 -5
  21. data/lib/qml/access.rb +9 -17
  22. data/lib/qml/application.rb +18 -21
  23. data/lib/qml/context.rb +1 -1
  24. data/lib/qml/dispatcher.rb +0 -1
  25. data/lib/qml/error_converter.rb +1 -0
  26. data/lib/qml/meta_object.rb +3 -3
  27. data/lib/qml/qt_object_base.rb +130 -5
  28. data/lib/qml/reactive/object.rb +34 -25
  29. data/lib/qml/reactive/signal.rb +6 -10
  30. data/lib/qml/reactive/unbound_property.rb +1 -1
  31. data/lib/qml/reactive/unbound_signal.rb +2 -2
  32. data/lib/qml/version.rb +1 -1
  33. data/spec/qml/reactive/object_spec.rb +14 -38
  34. data/spec/qml/reactive/signal_spec.rb +1 -1
  35. data/spec/qml/reactive/unbound_property_spec.rb +40 -0
  36. data/spec/qml/reactive/unbound_signal_spec.rb +70 -0
  37. data/spec/{shared_examples → shared}/qml/data/list_model.rb +0 -0
  38. data/spec/shared/qml/reactive/object.rb +39 -0
  39. data/spec/spec_helper.rb +1 -1
  40. metadata +12 -6
  41. data/lib/qml/class_builder.rb +0 -129
@@ -42,11 +42,7 @@ public:
42
42
  template <typename ... TArgs>
43
43
  RubyValue send(ID method, TArgs ... args) const
44
44
  {
45
- constexpr int argc = sizeof...(args);
46
- std::array<VALUE, argc> argv = {{ VALUE(args)... }};
47
- return protect([&] {
48
- return rb_funcall2(mValue, method, argc, argv.data());
49
- });
45
+ return rb_funcall(mValue, method, sizeof...(args), args...);
50
46
  }
51
47
 
52
48
  template <typename ... TArgs>
@@ -59,7 +55,11 @@ public:
59
55
  bool operator!=(const RubyValue &other) const { return !operator==(other); }
60
56
  explicit operator bool() const { return RTEST(mValue); }
61
57
 
62
- bool isKindOf(const RubyModule &module) const;
58
+ bool isKindOf(const RubyValue &module) const
59
+ {
60
+ return RTEST(rb_obj_is_kind_of(mValue, module));
61
+ }
62
+
63
63
  bool isConvertibleTo(int metaType) const;
64
64
  int defaultMetaType() const;
65
65
 
@@ -99,16 +99,8 @@ struct Conversion<bool>
99
99
  template <typename T>
100
100
  struct Conversion<T, typename std::enable_if<std::is_signed<T>::value && std::is_integral<T>::value>::type>
101
101
  {
102
- static T from(RubyValue x)
103
- {
104
- return protect([&] {
105
- return NUM2LL(x);
106
- });
107
- }
108
- static RubyValue to(T x)
109
- {
110
- return LL2NUM(x);
111
- }
102
+ static T from(RubyValue x) { return NUM2LL(x); }
103
+ static RubyValue to(T x) { return LL2NUM(x); }
112
104
  };
113
105
 
114
106
  // unsigned integers
@@ -116,16 +108,8 @@ struct Conversion<T, typename std::enable_if<std::is_signed<T>::value && std::is
116
108
  template <typename T>
117
109
  struct Conversion<T, typename std::enable_if<std::is_unsigned<T>::value && std::is_integral<T>::value>::type>
118
110
  {
119
- static T from(RubyValue x)
120
- {
121
- return protect([&] {
122
- return NUM2ULL(x);
123
- });
124
- }
125
- static RubyValue to(T x)
126
- {
127
- return ULL2NUM(x);
128
- }
111
+ static T from(RubyValue x) { return NUM2ULL(x); }
112
+ static RubyValue to(T x) { return ULL2NUM(x); }
129
113
  };
130
114
 
131
115
  // floating point values
@@ -135,14 +119,12 @@ struct Conversion<T, typename std::enable_if<std::is_floating_point<T>::value>::
135
119
  {
136
120
  static T from(RubyValue x)
137
121
  {
138
- return protect([&] {
139
- auto type = rb_type(x);
140
- if (type == T_FIXNUM || type == T_BIGNUM) {
141
- return double(NUM2LL(x));
142
- } else {
143
- return RFLOAT_VALUE(VALUE(x));
144
- }
145
- });
122
+ auto type = rb_type(x);
123
+ if (type == T_FIXNUM || type == T_BIGNUM) {
124
+ return double(NUM2LL(x));
125
+ } else {
126
+ return RFLOAT_VALUE(VALUE(x));
127
+ }
146
128
  }
147
129
  static RubyValue to(T x)
148
130
  {
@@ -161,9 +143,7 @@ struct Conversion<T<V>, typename std::enable_if<IsQListLike<T<V>>::value>::type>
161
143
  {
162
144
  static T<V> from(RubyValue x)
163
145
  {
164
- protect([&] {
165
- x = rb_convert_type(x, T_ARRAY, "Array", "to_ary");
166
- });
146
+ x = rb_convert_type(x, T_ARRAY, "Array", "to_ary");
167
147
  int length = RARRAY_LEN(VALUE(x));
168
148
  T<V> list;
169
149
  list.reserve(length);
@@ -174,14 +154,10 @@ struct Conversion<T<V>, typename std::enable_if<IsQListLike<T<V>>::value>::type>
174
154
  }
175
155
  static RubyValue to(const T<V> &list)
176
156
  {
177
- RubyValue ary = protect([&] {
178
- return rb_ary_new();
179
- });
157
+ auto ary = rb_ary_new();
180
158
  for (const auto &elem : list) {
181
159
  auto x = RubyValue::from(elem);
182
- protect([&] {
183
- rb_ary_push(ary, x);
184
- });
160
+ rb_ary_push(ary, x);
185
161
  }
186
162
  return ary;
187
163
  }
@@ -198,34 +174,25 @@ struct Conversion<T<K, V>, typename std::enable_if<IsQHashLike<T<K, V>>::value>:
198
174
  {
199
175
  static T<K, V> from(RubyValue x)
200
176
  {
201
- protect([&] {
202
- x = rb_convert_type(x, T_HASH, "Hash", "to_hash");
203
- });
177
+ x = rb_convert_type(x, T_HASH, "Hash", "to_hash");
178
+
204
179
  T<K, V> hash;
205
- protect([&] {
206
- auto each = [](VALUE key, VALUE value, VALUE arg) -> int {
207
- auto &hash = *reinterpret_cast<T<K, V> *>(arg);
208
- unprotect([&] {
209
- hash[RubyValue(key).to<K>()] = RubyValue(value).to<V>();
210
- });
211
- return ST_CONTINUE;
212
- };
213
- auto eachPtr = (int (*)(VALUE, VALUE, VALUE))each;
214
- rb_hash_foreach(x, (int (*)(...))eachPtr, (VALUE)(&hash));
215
- });
180
+ auto each = [](VALUE key, VALUE value, VALUE arg) -> int {
181
+ auto &hash = *reinterpret_cast<T<K, V> *>(arg);
182
+ hash[RubyValue(key).to<K>()] = RubyValue(value).to<V>();
183
+ return ST_CONTINUE;
184
+ };
185
+ auto eachPtr = (int (*)(VALUE, VALUE, VALUE))each;
186
+ rb_hash_foreach(x, (int (*)(...))eachPtr, (VALUE)(&hash));
216
187
  return hash;
217
188
  }
218
189
  static RubyValue to(const T<K, V> &hash)
219
190
  {
220
- RubyValue rubyHash = protect([&] {
221
- return rb_hash_new();
222
- });
191
+ auto rubyHash = rb_hash_new();
223
192
  for (auto i = hash.begin(); i != hash.end(); ++i) {
224
193
  auto k = RubyValue::from(i.key());
225
194
  auto v = RubyValue::from(i.value());
226
- protect([&] {
227
- rb_hash_aset(rubyHash, k, v);
228
- });
195
+ rb_hash_aset(rubyHash, k, v);
229
196
  }
230
197
  return rubyHash;
231
198
  }
@@ -56,9 +56,7 @@ void SignalForwarder::callProc(const QVariantList &list)
56
56
  if (mProcRef.hasValue()) {
57
57
  withGvl([&] {
58
58
  auto args = RubyValue::from(list);
59
- protect([&] {
60
- rb_funcall2(mProcRef.value(), rb_intern("call"), RARRAY_LEN(VALUE(args)), RARRAY_PTR(VALUE(args)));
61
- });
59
+ rb_apply(mProcRef.value(), RUBYQML_INTERN("call"), args);
62
60
  });
63
61
  }
64
62
  }
@@ -16,32 +16,14 @@ void *rb_thread_call_without_gvl(void *(*func)(void *), void *data1, rb_unblock_
16
16
 
17
17
  namespace RubyQml {
18
18
 
19
- void protect(const std::function<void ()> &doAction)
19
+ void convertCppErrors(const std::function<void ()> &doAction) noexcept
20
20
  {
21
- auto callback = [](VALUE data) {
22
- auto &doAction= *reinterpret_cast<const std::function<void ()> *>(data);
23
- doAction();
24
- return Qnil;
25
- };
26
- int state;
27
- rb_protect(callback, reinterpret_cast<VALUE>(&doAction), &state);
28
- if (state) {
29
- throw RubyException(state);
30
- }
31
- }
32
-
33
- void unprotect(const std::function<void ()> &doAction) noexcept
34
- {
35
- int state = 0;
36
21
  bool cppErrorOccured= false;
37
22
  VALUE cppErrorClassName = Qnil;
38
23
  VALUE cppErrorMessage = Qnil;
39
24
  try {
40
25
  doAction();
41
26
  }
42
- catch (const RubyException &ex) {
43
- state = ex.state();
44
- }
45
27
  catch (const std::exception &ex) {
46
28
  cppErrorOccured = true;
47
29
  int status;
@@ -50,9 +32,6 @@ void unprotect(const std::function<void ()> &doAction) noexcept
50
32
  free(classname);
51
33
  cppErrorMessage = rb_str_new_cstr(ex.what());
52
34
  }
53
- if (state) {
54
- rb_jump_tag(state);
55
- }
56
35
  if (cppErrorOccured) {
57
36
  auto patterns = rb_funcall(rb_path2class("QML::ErrorConverter"), rb_intern("patterns"), 0);
58
37
  auto rubyClass = rb_hash_aref(patterns, cppErrorClassName);
@@ -127,9 +106,7 @@ void withGvl(const std::function<void ()> &doAction)
127
106
  void fail(const char *errorClassName, const QString &message)
128
107
  {
129
108
  auto msg = message.toUtf8();
130
- protect([&] {
131
- rb_raise(rb_path2class(errorClassName), "%s", msg.data());
132
- });
109
+ rb_raise(rb_path2class(errorClassName), "%s", msg.data());
133
110
  }
134
111
 
135
112
  }
@@ -54,27 +54,7 @@ private:
54
54
  int mState = 0;
55
55
  };
56
56
 
57
-
58
- // Convert Ruby exceptions into C++ exceptions (RubyException)
59
- void protect(const std::function<void()> &doAction);
60
-
61
- template <typename F>
62
- typename std::enable_if<
63
- !std::is_same<typename std::result_of<F()>::type, void>::value,
64
- typename std::result_of<F()>::type>::type
65
- protect(const F &doAction)
66
- {
67
- typename std::result_of<F()>::type ret;
68
- protect([&] {
69
- ret = doAction();
70
- });
71
- return ret;
72
- }
73
-
74
- // Regenerate Ruby exceptions that are converted into RubyException
75
- // and convert std::exception exceptions into Ruby errors.
76
- // Other C++ exceptions are not allowed to be thrown out of this function.
77
- void unprotect(const std::function<void()> &doAction) noexcept;
57
+ void convertCppErrors(const std::function<void()> &doAction) noexcept;
78
58
 
79
59
  void rescue(const std::function<void ()> &doAction, const std::function<void (RubyValue)> &handleException);
80
60
  void rescueNotify(const std::function<void ()> &doAction);
@@ -32,11 +32,10 @@ WeakValueReference::WeakValueReference(RubyValue value) :
32
32
  d->mHasValue = true;
33
33
  d->mValue = value;
34
34
  static auto objspace = RubyModule::fromPath("ObjectSpace");
35
- protect([&] {
36
- auto proc = rb_proc_new((VALUE (*)(...))&Data::finalize, Ext_AnyWrapper::create(QVariant::fromValue(d)));
37
- VALUE args[] = { value };
38
- rb_funcall_with_block(objspace, RUBYQML_INTERN("define_finalizer"), 1, args, proc);
39
- });
35
+
36
+ auto proc = rb_proc_new((VALUE (*)(...))&Data::finalize, Ext_AnyWrapper::create(QVariant::fromValue(d)));
37
+ VALUE args[] = { value };
38
+ rb_funcall_with_block(objspace, RUBYQML_INTERN("define_finalizer"), 1, args, proc);
40
39
  }
41
40
 
42
41
  bool WeakValueReference::hasValue() const
@@ -11,13 +11,11 @@ module QML
11
11
  include Dispatchable
12
12
  include Wrappable
13
13
  # @!parse include Reactive::Object
14
- # @!parse include SignalInitialization
15
14
  # @!parse extend ClassMethods
16
15
 
17
16
  def self.included(derived)
18
17
  derived.class_eval do
19
18
  include Reactive::Object
20
- include SignalInitialization
21
19
  extend ClassMethods
22
20
  end
23
21
  end
@@ -113,26 +111,20 @@ module QML
113
111
  end
114
112
  end
115
113
 
116
- module SignalInitialization
117
- def initialize(*args, &block)
118
- super
119
- signal_names = signals + properties.map { |name| :"#{name}_changed" }
120
- signal_names.each do |name|
121
- __send__(name).connect do |*args|
122
- @access_wrappers.each do |obj|
123
- obj.class.meta_object.invoke_method(obj.pointer, name, args)
124
- end
125
- end
126
- end
127
- end
128
- end
129
-
130
114
  # @api private
131
115
  attr_reader :access_wrappers
132
116
 
133
117
  def initialize(*args, &block)
134
- super
118
+ signal_names = signals + properties.map { |name| :"#{name}_changed" }
119
+ signal_names.each do |name|
120
+ __send__(name).connect do |*args|
121
+ @access_wrappers.each do |obj|
122
+ obj.class.meta_object.invoke_method(obj.pointer, name, args)
123
+ end
124
+ end
125
+ end
135
126
  @access_wrappers = []
127
+ super
136
128
  end
137
129
 
138
130
  def create_wrapper
@@ -106,29 +106,26 @@ module QML
106
106
  end
107
107
  end
108
108
 
109
- # @overload application
110
- # Returns the instance of {Application}.
111
- # @return [Application]
112
- # @overload application
113
- # Call {init} if ruby-qml is not initialized, then yields the application instance and then call {Application#exec}.
114
- # @return [Application]
115
- # @example
116
- # QML.application do |app|
117
- # app.context[:foo] = 'foo'
118
- # app.load_path Pathname(__FILE__) + '../main.qml'
119
- # end
120
- # @see Application.instance
109
+ # Returns the instance of {Application}.
110
+ # @return [Application]
121
111
  def application
122
- if block_given?
123
- QML.init unless QML.initialized?
124
- Application.instance.tap do |app|
125
- yield app
126
- app.exec
127
- end
128
- else
129
- Application.instance
112
+ Application.instance
113
+ end
114
+
115
+ # Call {init} if ruby-qml is not initialized, then yields the application instance and then call {Application#exec}.
116
+ # @return [Application]
117
+ # @example
118
+ # QML.run do |app|
119
+ # app.context[:foo] = 'foo'
120
+ # app.load_path Pathname(__FILE__) + '../main.qml'
121
+ # end
122
+ def run
123
+ QML.init unless QML.initialized?
124
+ Application.instance.tap do |app|
125
+ yield app
126
+ app.exec
130
127
  end
131
128
  end
132
129
 
133
- module_function :application
130
+ module_function :application, :run
134
131
  end
@@ -10,7 +10,7 @@ module QML
10
10
  # @see http://qt-project.org/doc/qt-5/qqmlcontext.html QQmlContext(C++)
11
11
  #
12
12
  # @example
13
- # QML.application do |app|
13
+ # QML.run do |app|
14
14
  # app.context[:foo] = 'foo'
15
15
  # app.context[:bar] = 'bar'
16
16
  # ...
@@ -43,7 +43,6 @@ module QML
43
43
 
44
44
  on_init do
45
45
  Kernel.event_loop_hook_timer.timeout.connect do
46
- puts "======== run tasks"
47
46
  Dispatcher.instance.run_tasks
48
47
  end
49
48
  end
@@ -11,5 +11,6 @@ module QML
11
11
  end
12
12
 
13
13
  add_pattern 'RubyQml::QmlException', QMLError
14
+ add_pattern 'RubyQml::ConversionError', ConversionError
14
15
  end
15
16
  end
@@ -1,5 +1,5 @@
1
1
  require 'qml/qml'
2
- require 'qml/class_builder'
2
+ require 'qml/qt_object_base'
3
3
 
4
4
  module QML
5
5
  class MetaObject
@@ -12,9 +12,9 @@ module QML
12
12
  def build_class
13
13
  @@classes ||= {}
14
14
  klass = @@classes[name]
15
- builder = ClassBuilder.new(self, klass)
15
+ builder = QtObjectBase::SubclassBuilder.new(self, klass)
16
16
  builder.build
17
- @@classes[name] = builder.klass
17
+ @@classes[name] = builder.subclass
18
18
  end
19
19
  end
20
20
  end
@@ -1,7 +1,50 @@
1
1
  require 'qml/dispatchable'
2
+ require 'qml/name_helper'
2
3
 
3
4
  module QML
4
5
 
6
+ class QtPropertyBase
7
+ attr_reader :changed
8
+
9
+ def initialize(metaobj, objptr, name)
10
+ super()
11
+ @metaobj = metaobj
12
+ @objptr = objptr
13
+ @name = name
14
+ @changed = QtSignal.new(metaobj, objptr, @metaobj.notify_signal(@name))
15
+ end
16
+
17
+ def value=(newval)
18
+ @metaobj.set_property(@objptr, @name, newval)
19
+ end
20
+
21
+ def value
22
+ @metaobj.get_property(@objptr, @name)
23
+ end
24
+ end
25
+
26
+ class QtProperty < QtPropertyBase
27
+ include Reactive::Bindable
28
+ end
29
+
30
+ class QtSignal < Reactive::Signal
31
+ def initialize(metaobj, objptr, name)
32
+ super(nil)
33
+ @objptr = objptr
34
+ @metaobj = metaobj
35
+ @name = name
36
+ @initialized = false
37
+ end
38
+
39
+ private
40
+
41
+ def connected(block)
42
+ return if @initialized
43
+ @metaobj.connect_signal(@objptr, @name, method(:emit))
44
+ @initialized = true
45
+ end
46
+ end
47
+
5
48
  # {QtObjectBase} is the base class for Qt object wrappers.
6
49
  #
7
50
  # In ruby-qml you can access the followings of Qt objects in Ruby.
@@ -33,6 +76,93 @@ module QML
33
76
  include Dispatchable
34
77
  include Reactive::Object
35
78
 
79
+ # @api private
80
+ class SubclassBuilder
81
+
82
+ attr_reader :subclass
83
+
84
+ def initialize(metaobj, klass)
85
+ @metaobj = metaobj
86
+ @subclass = klass
87
+ end
88
+
89
+ def build
90
+ create unless @subclass
91
+ return if @subclass.meta_object == @metaobj
92
+
93
+ @metaobj.method_names.reject { |name| @metaobj.signal?(name) }.each do |name|
94
+ define_method(name)
95
+ end
96
+ @metaobj.method_names.select { |name| @metaobj.signal?(name) }.each do |name|
97
+ define_signal(name)
98
+ end
99
+ @metaobj.property_names.each do |name|
100
+ define_property(name)
101
+ end
102
+ @metaobj.enumerators.each do |k, v|
103
+ define_enum(k, v)
104
+ end
105
+ @subclass.__send__ :meta_object=, @metaobj
106
+
107
+ self
108
+ end
109
+
110
+ private
111
+
112
+ def create
113
+ super_metaobj = @metaobj.super_class
114
+ @subclass = Class.new(super_metaobj ? super_metaobj.build_class : QtObjectBase)
115
+ end
116
+
117
+ def define_method(name)
118
+ metaobj = @metaobj
119
+ return if metaobj.private?(name)
120
+ underscore = NameHelper.to_underscore(name)
121
+
122
+ @subclass.class_eval do
123
+ define_method name do |*args|
124
+ metaobj.invoke_method(@pointer, name, args)
125
+ end
126
+ private name if metaobj.protected?(name)
127
+ alias_method underscore, name unless underscore == name
128
+ end
129
+ end
130
+
131
+ def define_signal(name)
132
+ metaobj = @metaobj
133
+ underscore = NameHelper.to_underscore(name)
134
+ @subclass.class_eval do
135
+ variadic_signal name, signal: -> { QtSignal.new(metaobj, @pointer, name) }
136
+ alias_signal underscore, name unless underscore == name
137
+ end
138
+ end
139
+
140
+ def define_property(name)
141
+ metaobj = @metaobj
142
+ underscore = NameHelper.to_underscore(name)
143
+ @subclass.class_eval do
144
+ property name, nil, property: -> { QtProperty.new(metaobj, @pointer, name) }
145
+ alias_property underscore, name unless underscore == name
146
+ end
147
+ end
148
+
149
+ def define_enum(name, hash)
150
+ define_const(name, hash.values.sort)
151
+ hash.each do |k, v|
152
+ define_const(k, v)
153
+ end
154
+ end
155
+
156
+ def define_const(name, value)
157
+ name = (name[0].capitalize + name[1..-1]).to_sym
158
+ underscore = NameHelper.to_upper_underscore(name)
159
+ @subclass.class_eval do
160
+ const_set name, value
161
+ const_set underscore, value unless underscore == name
162
+ end
163
+ end
164
+ end
165
+
36
166
  class << self
37
167
  attr_accessor :meta_object
38
168
  private :meta_object=
@@ -41,11 +171,6 @@ module QML
41
171
  attr_accessor :pointer
42
172
  private :pointer=
43
173
 
44
- # @api private
45
- def custom_data
46
- @custom_data ||= {}
47
- end
48
-
49
174
  # Evaluates a JavaScript expression on the QML context of the object.
50
175
  # Fails with {QMLError} when the object is not created by QML and does not belong to any context.
51
176
  # @param [String] str