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,9 @@
1
+ require 'qml/plugins'
2
+
3
+ module QML
4
+ Application = Kernel.application_meta_object.build_class
5
+ Engine = Kernel.engine_meta_object.build_class
6
+ Context = Plugins.core.metaObjects['QQmlContext'].build_class
7
+ Component = Plugins.core.metaObjects['QQmlComponent'].build_class
8
+ Qt = Plugins.core.metaObjects['Qt'].build_class
9
+ end
@@ -0,0 +1,108 @@
1
+ require 'qml/dispatchable'
2
+
3
+ module QML
4
+
5
+ # {QtObjectBase} is the base class for Qt object wrappers.
6
+ #
7
+ # In ruby-qml you can access the followings of Qt objects in Ruby.
8
+ #
9
+ # * Properties as {Reactive::Property}
10
+ # * Signals as {Reactive::Signal}
11
+ # * Q_INVOKABLE methods, slots and QML methods as Ruby methods
12
+ #
13
+ # Properties and signals support is provided by {Reactive::Object}.
14
+ #
15
+ # While their names are camelCase in Qt, ruby-qml aliases them as underscore_case.
16
+ #
17
+ # @example
18
+ # # QML::Application is actually a wrapper for QApplication
19
+ # app = QML.application
20
+ #
21
+ # # set property
22
+ # app.applicationName = "Test"
23
+ # app.application_name = "Test" # aliased version
24
+ #
25
+ # # connect to signal
26
+ # app.aboutToQuit.connect do # "about_to_quit" is also OK
27
+ # puts "quitting..."
28
+ # end
29
+ #
30
+ # # call slot
31
+ # app.quit
32
+ class QtObjectBase
33
+ include Dispatchable
34
+ include Reactive::Object
35
+
36
+ class << self
37
+ attr_accessor :meta_object
38
+ private :meta_object=
39
+ end
40
+
41
+ attr_accessor :pointer
42
+ private :pointer=
43
+
44
+ # @api private
45
+ def custom_data
46
+ @custom_data ||= {}
47
+ end
48
+
49
+ # Evaluates a JavaScript expression on the QML context of the object.
50
+ # Fails with {QMLError} when the object is not created by QML and does not belong to any context.
51
+ # @param [String] str
52
+ # @return the result.
53
+ # @example
54
+ # component = QML::Component.new data: <<-EOS
55
+ # import QtQuick 2.0
56
+ # QtObject {
57
+ # property string foo: 'foo'
58
+ # property string bar: 'bar'
59
+ # }
60
+ # EOS
61
+ # obj = component.create
62
+ # obj.qml_eval("foo + bar") #=> "foobar"
63
+ def qml_eval(str)
64
+ context = Context.for_object(self)
65
+ fail QMLError, 'belongs to no context' unless context
66
+ context.eval(self, str)
67
+ end
68
+
69
+ def inspect
70
+ klass = self.class
71
+ property_inspect = klass.instance_properties.sort
72
+ .reject { |name| klass.instance_property(name).alias? }
73
+ .map do |name|
74
+ "#{name}=" +
75
+ begin
76
+ property(name).value.inspect
77
+ rescue ConversionError
78
+ "<unsupported type>"
79
+ end
80
+ end
81
+ .join(' ')
82
+ classname = klass.name || "[class for #{klass.meta_object.name}]"
83
+ "#<#{classname}:#{__id__} #{property_inspect}>"
84
+ end
85
+
86
+ alias_method :to_s, :inspect
87
+
88
+ # @return whether the object is managed by Ruby and QML and garbage collected when no longer used.
89
+ def managed?
90
+ pointer.managed?
91
+ end
92
+
93
+ # Sets the management status of the object.
94
+ # @param [Boolean] managed
95
+ # @return [Boolean]
96
+ def managed=(managed)
97
+ pointer.managed = managed
98
+ end
99
+
100
+ # Sets the management status of the object in safer way.
101
+ # Objects that are created by QML will remain managed and objects that have parents will remain unmanaged.
102
+ # @param [Boolean] managed
103
+ # @return [Boolean]
104
+ def prefer_managed(managed)
105
+ pointer.prefer_managed managed
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,8 @@
1
+ require 'qml/reactive/signal'
2
+ require 'qml/reactive/signals/map_signal'
3
+ require 'qml/reactive/signals/select_signal'
4
+ require 'qml/reactive/signals/merge_signal'
5
+ require 'qml/reactive/signal_spy'
6
+ require 'qml/reactive/property'
7
+ require 'qml/reactive/object'
8
+ require 'qml/reactive/error'
@@ -0,0 +1,79 @@
1
+ module QML
2
+ module Reactive
3
+ module Bindable
4
+
5
+ def initialize(*args, &block)
6
+ super
7
+ @connections = []
8
+ end
9
+
10
+ # Sets a new value.
11
+ # The old property binding is discarded.
12
+ # @param new_value new value
13
+ def value=(new_value, unbind = true)
14
+ self.unbind if unbind
15
+ unless value == new_value
16
+ super(new_value)
17
+ changed.emit(new_value)
18
+ end
19
+ new_value
20
+ end
21
+
22
+ alias_method :set_value, :value=
23
+
24
+ # @return The property value
25
+ def value
26
+ touch
27
+ super
28
+ end
29
+
30
+ # @api private
31
+ module Resolver
32
+ @sources_stack = []
33
+ class << self
34
+ def eval_and_resolve(&block)
35
+ @sources_stack.push([])
36
+ ret = block.call
37
+ [ret, @sources_stack.pop]
38
+ end
39
+
40
+ def add(property)
41
+ current = @sources_stack.last
42
+ current && current << property
43
+ end
44
+ end
45
+ end
46
+
47
+ # Sets a property binding.
48
+ # The block is re-evaluated when any of other properties used in it is updated
49
+ # and the result is used as the new property value.
50
+ # @yield
51
+ # @example
52
+ # p1 = Property.new
53
+ # p2 = Property.new
54
+ # p1.bind { p2.value + 1 }
55
+ # p2.value = 10
56
+ # p1.value #=> 11
57
+ # @return The property value
58
+ def bind(&block)
59
+ unbind
60
+ value, sources = Resolver.eval_and_resolve(&block)
61
+ set_value(value, false)
62
+ @connections = sources.map do |source|
63
+ fail Error, 'Recursive binding' if source == self
64
+ source.changed.connect { set_value(block.call, false) }
65
+ end
66
+ end
67
+
68
+ def unbind
69
+ @connections.each(&:disconnect)
70
+ end
71
+
72
+ private
73
+
74
+ def touch
75
+ Resolver.add(self)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,25 @@
1
+ require 'qml/reactive/signal'
2
+
3
+ module QML
4
+ module Reactive
5
+ class ChainedSignal < Signal
6
+ private
7
+ def connected(listener)
8
+ if connection_count == 1
9
+ @connections = Array(connect_to_sources)
10
+ end
11
+ end
12
+
13
+ def disconnected(listener)
14
+ if connection_count == 0
15
+ @connections.each(&:disconnect)
16
+ @connections = nil
17
+ end
18
+ end
19
+
20
+ def connect_to_sources
21
+ fail NotImplementedError
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module QML
2
+ module Reactive
3
+ class Error < ::StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,278 @@
1
+ require 'qml/reactive/unbound_signal'
2
+ require 'qml/reactive/unbound_property'
3
+
4
+ module QML
5
+ module Reactive
6
+
7
+ # {Object} is used to define signals and properties within class definition.
8
+ # Signals and properties will be inherited by and can be overridden in subclasses.
9
+ # @note Currently {Object} can be included only by classes, not modules.
10
+ # @see Object::ClassMethods
11
+ module Object
12
+
13
+ # When {Object} is included by a class, the class extends {ClassMethods} to add the common class methods.
14
+ def self.included(derived)
15
+ fail Error, "SignalDef must be included in a class" unless derived.is_a? ::Class
16
+ derived.extend(ClassMethods)
17
+ end
18
+
19
+ def initialize(*args, &block)
20
+ super
21
+ properties = self.class.instance_property_hash
22
+ .values
23
+ .map { |property| self.class.instance_property(property.original) }
24
+ .uniq
25
+ notifier_signals = properties.map(&:notifier_signal)
26
+ signals = self.class.instance_signal_hash
27
+ .values
28
+ .map { |signal| self.class.instance_signal(signal.original) }
29
+ .uniq - notifier_signals
30
+
31
+ properties.each do |property|
32
+ p = property.bind(self)
33
+ instance_variable_set(:"@_property_#{property.name}", p)
34
+ instance_variable_set(:"@_signal_#{property.notifier_signal.name}", p.changed)
35
+ end
36
+ signals.each do |signal|
37
+ instance_variable_set(:"@_signal_#{signal.name}", signal.bind(self))
38
+ end
39
+
40
+ self.class.send(:initial_connections_hash).each do |name, blocks|
41
+ blocks.each do |block|
42
+ signal(name).connect do |*args|
43
+ instance_exec(*args, &block)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # @!method signal(name)
50
+ # Returns a signal.
51
+ # @param name [Symbol] The signal name
52
+ # @return [QML::Reactive::Signal]
53
+
54
+ # @!method signals
55
+ # Returns all signal names.
56
+ # @return [Array<Symbol>]
57
+
58
+ # @!method property(name)
59
+ # Returns a property.
60
+ # @param name [Symbol] The property name
61
+ # @return [QML::Reactive::Property]
62
+
63
+ # @!method properties
64
+ # Returns all property names.
65
+ # @return [Array<Symbol>]
66
+
67
+ [%w{signal signals}, %w{property properties}].each do |singular, plural|
68
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
69
+ def #{singular}(name)
70
+ name = self.class.instance_#{singular}(name).original
71
+ instance_variable_get(:"@_#{singular}_\#{name}") or
72
+ fail ::NameError, "undefined #{singular} '\#{name}' for class '\#{self.class}'"
73
+ end
74
+
75
+ def #{plural}
76
+ self.class.instance_#{plural}
77
+ end
78
+ EOS
79
+ end
80
+
81
+ # @!parse extend ClassMethods
82
+
83
+ module ClassMethods
84
+
85
+ # @!method instance_signals(include_super = true)
86
+ # Returns all signal names for the class.
87
+ # @param include_super [true|false]
88
+ # @return [Array<Symbol>]
89
+
90
+ # @!method instance_signal(name)
91
+ # Returns an unbound signal.
92
+ # @param name [Symbol] The signal name
93
+ # @return [QML::Reactive::UnboundSignal]
94
+
95
+ # @!method instance_properties(include_super = true)
96
+ # Returns all property names for the class.
97
+ # @param include_super [true|false]
98
+ # @return [Array<Symbol>]
99
+
100
+ # @!method instance_property(name)
101
+ # Returns an unbound property.
102
+ # @param name [Symbol] The property name
103
+ # @return [QML::Reactive::UnboundProperty]
104
+ [%w{signal signals}, %w{property properties}].each do |singular, plural|
105
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
106
+ def instance_#{singular}_hash(include_super = true)
107
+ if include_super && superclass.include?(Object)
108
+ superclass.instance_#{singular}_hash.merge instance_#{singular}_hash(false)
109
+ else
110
+ @instance_#{singular}_hash ||= {}
111
+ end
112
+ end
113
+
114
+ def instance_#{plural}(include_super = true)
115
+ instance_#{singular}_hash(include_super).keys
116
+ end
117
+
118
+ def instance_#{singular}(name)
119
+ instance_#{singular}_hash[name] or fail ::NameError, "undefined #{singular} '\#{name}' for class '\#{self}'"
120
+ end
121
+ EOS
122
+ end
123
+
124
+ # Defines a signal for the class.
125
+ # The signal will be variadic if args == nil.
126
+ # @param name [#to_sym] The signal name
127
+ # @param params [Array<#to_sym>, nil] The signal parameter names
128
+ # @return [Symbol] The signal name
129
+ # @example
130
+ # class Button
131
+ # include QML::Reactive::Object
132
+ # signal :pressed, [:pos]
133
+ # def press(pos)
134
+ # pressed.emit(pos)
135
+ # end
136
+ # end
137
+ #
138
+ # button = Button.new
139
+ # button.pressed.connect { |pos| puts "Pressed at #{pos}" }
140
+ # button.press([10, 20])
141
+ #
142
+ # class ColorButton < Button
143
+ # signal :pressed, [:pos, :color]
144
+ # end
145
+ #
146
+ # color_button = ColorButton.new
147
+ # color_button.pressed.connect { |pos, color| "#{color} button pressed at #{pos}" }
148
+ # color_button.press([10, 20], 'red')
149
+ def signal(name, params, factory: nil)
150
+ name.to_sym.tap do |name|
151
+ params = params.map(&:to_sym)
152
+ add_signal(UnboundSignal.new(name, params, false, self, factory))
153
+ end
154
+ end
155
+
156
+ # Defines a variadic signal.
157
+ # Variadic signals do not restrict the number of arguments.
158
+ # @see #signal
159
+ def variadic_signal(name, factory: nil)
160
+ name.to_sym.tap do |name|
161
+ add_signal(UnboundSignal.new(name, nil, true, self, factory))
162
+ end
163
+ end
164
+
165
+ # Aliases a signal.
166
+ # @return [Symbol] The new name
167
+ def alias_signal(name, original_name)
168
+ add_signal(instance_signal(original_name).alias(name))
169
+ name
170
+ end
171
+
172
+ # Defines a property for the class.
173
+ # @param name [#to_sym] The name of the property
174
+ # @param init_value The initial value (optional)
175
+ # @yield The initial property binding (optional)
176
+ # @return [Symbol] The name
177
+ # @example
178
+ # class Foo
179
+ # include QML::Reactive::Object
180
+ # property(:name) { 'hogehoge' }
181
+ # ...
182
+ # end
183
+ # Foo.new.name #=> 'hogehoge'
184
+ # Foo.new.name = 'foobar'
185
+ # Foo.new.name #=> 'foobar'
186
+ # Foo.new.name_changed.connect do |new_name|
187
+ # ...
188
+ #
189
+ # class Bar < Foo
190
+ # property(:name) { 'piyopiyo' }
191
+ # end
192
+ # Bar.new.name #=> 'piyopiyo'
193
+ def property(name, init_value = nil, factory: nil, &init_binding)
194
+ name = name.to_sym
195
+ add_property(UnboundProperty.new(name, init_value, init_binding, self, factory))
196
+ name
197
+ end
198
+
199
+ # Aliases a property.
200
+ # @return [Symbol] The new name
201
+ def alias_property(name, original_name)
202
+ add_property(instance_property(original_name).alias(name))
203
+ name
204
+ end
205
+
206
+ # Adds a signal handler.
207
+ # @param signal_name The name of the signal
208
+ # @yield The block that is connected to the signal during object initialization
209
+ def on(signal_name, &block)
210
+ # just for check
211
+ instance_signal(signal_name)
212
+ @initial_connections_hash ||= {}
213
+ @initial_connections_hash[signal_name] ||= []
214
+ @initial_connections_hash[signal_name] << block
215
+ end
216
+
217
+ # Adds a handler to a property change signal.
218
+ # @param property_name The name of the property
219
+ # @yield The block that is connected to the property change signal during object initialization
220
+ # @example
221
+ # class Foo
222
+ # property :bar, ''
223
+ # on_changed :bar do
224
+ # some_action
225
+ # end
226
+ # def some_action
227
+ # ...
228
+ # end
229
+ # end
230
+ # @see #on
231
+ def on_changed(property_name, &block)
232
+ on(:"#{property_name}_changed", &block)
233
+ end
234
+
235
+ private
236
+
237
+ def add_signal(signal)
238
+ instance_signal_hash(false)[signal.name] = signal
239
+
240
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
241
+ def #{signal.name}
242
+ @_signal_#{signal.original}
243
+ end
244
+ EOS
245
+ end
246
+
247
+ def add_property(property)
248
+ instance_property_hash(false)[property.name] = property
249
+
250
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
251
+ def #{property.name}(&block)
252
+ @_property_#{property.original}.value(&block)
253
+ end
254
+ def #{property.name}=(new_value)
255
+ @_property_#{property.original}.value = new_value
256
+ end
257
+ EOS
258
+
259
+ add_signal(property.notifier_signal)
260
+ end
261
+
262
+ def initial_connections_hash(include_super: true)
263
+ if include_super && superclass.include?(Object)
264
+ superclass.send(:initial_connections_hash).dup.tap do |hash|
265
+ initial_connections_hash(include_super: false).each do |key, blocks|
266
+ hash[key] ||= []
267
+ hash[key] += blocks
268
+ end
269
+ end
270
+ else
271
+ @initial_connections_hash ||= {}
272
+ end
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
278
+