qml 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+