qt 0.1.6 → 0.1.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58b685a5934b6ab65de8085da77093cf9de7b608a2f047638e6662ece29f4f8a
4
- data.tar.gz: 3ce44932b8d818b1068908d73189704bdd4b1e2f54e6d2e462c83d217b81c3f3
3
+ metadata.gz: be7294fb17e8d0cd4eeb66cc86fe6ee91940a95d5a6f3afaf9dfa44fb54445ba
4
+ data.tar.gz: 20683a100daf8ca9299a2227ae2605acbde2393e2444d020625c058cbc76993c
5
5
  SHA512:
6
- metadata.gz: aac76dc37741b86a72be2b8b5f1d671b3f0e4fc59ad95e2958264c7a9929f3dad7d445e3bded8f46d67bc304296b0610d7f427ca19eebaec99fd41dff4d454cb
7
- data.tar.gz: 50d80970f96868a576ac445b84bbf45ea002bf8c55038b52d3e0c1f2a522d89c6cf2e2120a895b43963cc912900ab63a3520be8d91278d3a8f5a6ec08e364569
6
+ metadata.gz: 151a6423964153f8e5f54cba0cc7c54d1ff6b4a7fbd10b003d2a19f6263831493d18ed6fbaeabf88d5791d3e3326e356ba50e0d5332e212c75341bf6ea3c2f50
7
+ data.tar.gz: 9e7542fc4bc481b7c3110f4b55aa4b33dd327babbe4aaad3dc4078ce2bd404a97a90022f1e49d97bd3ae05cb805e4db8fd0aef13e368974505dc361d29edfdd9
@@ -1,6 +1,7 @@
1
1
  #include "qt_ruby_runtime.hpp"
2
2
 
3
3
  #include <QByteArray>
4
+ #include <QCoreApplication>
4
5
  #include <QDateTimeEdit>
5
6
  #include <QMetaMethod>
6
7
  #include <QObject>
@@ -29,6 +30,11 @@ std::unordered_map<QObject*, SignalHandlersByIndex>& signal_handlers() {
29
30
  return handlers;
30
31
  }
31
32
 
33
+ QObject* signal_callback_context() {
34
+ static QObject fallback_context;
35
+ return QCoreApplication::instance() ? static_cast<QObject*>(QCoreApplication::instance()) : &fallback_context;
36
+ }
37
+
32
38
  int resolve_signal_index(QObject* obj, const char* signal_name) {
33
39
  if (!obj || !signal_name) {
34
40
  return -1;
@@ -122,7 +128,25 @@ int QtRubyRuntime::qobject_connect_signal(void* object_handle, const char* signa
122
128
  }
123
129
 
124
130
  const QMetaMethod signal_method = obj->metaObject()->method(signal_index);
125
- auto* mapper = new QSignalMapper(obj);
131
+ if (signal_method.name() == "destroyed") {
132
+ QMetaObject::Connection direct_connection =
133
+ QObject::connect(obj, &QObject::destroyed, signal_callback_context(), [obj, signal_index](QObject*) {
134
+ if (!signal_callback_ref()) {
135
+ return;
136
+ }
137
+ signal_callback_ref()(obj, signal_index, signal_payload_for(obj, signal_index));
138
+ });
139
+
140
+ if (!direct_connection) {
141
+ return -4;
142
+ }
143
+
144
+ auto& by_index = signal_handlers()[obj];
145
+ by_index[signal_index].push_back(SignalHandler{signal_index, direct_connection, {}, nullptr});
146
+ return signal_index;
147
+ }
148
+
149
+ auto* mapper = new QSignalMapper(QCoreApplication::instance());
126
150
  mapper->setMapping(obj, signal_index);
127
151
 
128
152
  const int map_slot_index = mapper->metaObject()->indexOfSlot("map()");
@@ -146,6 +170,8 @@ int QtRubyRuntime::qobject_connect_signal(void* object_handle, const char* signa
146
170
  signal_callback_ref()(obj, mapped_signal_index, signal_payload_for(obj, mapped_signal_index));
147
171
  });
148
172
 
173
+ QObject::connect(obj, &QObject::destroyed, mapper, [mapper]() { mapper->deleteLater(); });
174
+
149
175
  if (!mapped_connection) {
150
176
  QObject::disconnect(signal_connection);
151
177
  mapper->deleteLater();
@@ -30,22 +30,30 @@ module Qt
30
30
  ensure_signal_callback!
31
31
 
32
32
  handle = widget_handle(widget) || raise(ArgumentError, 'widget handle is required')
33
- per_signal = prepare_signal_registration(handle, signal_name)
33
+ per_signal = prepare_signal_registration(@signal_handlers ||= {}, handle, signal_name)
34
34
  per_signal[:blocks] << block
35
35
  true
36
36
  end
37
37
 
38
- def prepare_signal_registration(handle, signal_name)
38
+ def on_internal_signal(widget, signal_name, &block)
39
+ raise ArgumentError, 'pass block to on_internal_signal' unless block
40
+
41
+ ensure_native_bridge_ready!
42
+ ensure_signal_callback!
43
+
44
+ handle = widget_handle(widget) || raise(ArgumentError, 'widget handle is required')
45
+ per_signal = prepare_signal_registration(@internal_signal_handlers ||= {}, handle, signal_name)
46
+ per_signal[:blocks] << block
47
+ true
48
+ end
49
+
50
+ def prepare_signal_registration(signal_store, handle, signal_name)
39
51
  signal_key = signal_name.to_s
40
52
  raise ArgumentError, 'signal name is required' if signal_key.empty?
41
53
 
42
- @signal_handlers ||= {}
43
- per_signal = ((@signal_handlers[handle.address] ||= {})[signal_key] ||= { index: nil, blocks: [] })
54
+ per_signal = ((signal_store[handle.address] ||= {})[signal_key] ||= { index: nil, blocks: [] })
44
55
  if per_signal[:index].nil?
45
- index = Qt::Native.qobject_connect_signal(handle, signal_key)
46
- raise ArgumentError, "failed to connect signal #{signal_key.inspect} (code=#{index})" if index.negative?
47
-
48
- per_signal[:index] = index
56
+ per_signal[:index] = acquire_signal_registration(handle, signal_key)
49
57
  end
50
58
  per_signal
51
59
  end
@@ -59,9 +67,14 @@ module Qt
59
67
  return false if per_widget.nil?
60
68
 
61
69
  signal_key = signal_name&.to_s
62
- per_widget.delete(signal_key) if signal_key
63
- per_widget.clear unless signal_key
64
- Qt::Native.qobject_disconnect_signal(handle, signal_key)
70
+ if signal_key
71
+ per_widget.delete(signal_key)
72
+ release_signal_registration(handle, signal_key)
73
+ else
74
+ per_widget.keys.each { |registered_signal| release_signal_registration(handle, registered_signal) }
75
+ per_widget.clear
76
+ end
77
+ @signal_handlers.delete(handle.address) if per_widget.empty?
65
78
  true
66
79
  end
67
80
 
@@ -103,7 +116,9 @@ module Qt
103
116
 
104
117
  @signal_callback = FFI::Function.new(:void, %i[pointer int string]) do |object_handle, signal_index, payload|
105
118
  normalized_payload = payload.nil? ? nil : Qt::StringCodec.from_qt_text(payload)
106
- EventRuntimeDispatch.dispatch_signal(@signal_handlers, object_handle, signal_index, normalized_payload)
119
+ EventRuntimeDispatch.dispatch_signal(
120
+ @internal_signal_handlers, @signal_handlers, object_handle, signal_index, normalized_payload
121
+ )
107
122
  end
108
123
 
109
124
  Qt::Native.set_signal_callback(@signal_callback)
@@ -141,5 +156,42 @@ module Qt
141
156
  @event_handlers.delete(handle.address)
142
157
  end
143
158
  end
159
+
160
+ def acquire_signal_registration(handle, signal_key)
161
+ @signal_registrations ||= {}
162
+ per_widget = (@signal_registrations[handle.address] ||= {})
163
+ registration = (per_widget[signal_key] ||= { index: nil, refcount: 0 })
164
+ if registration[:index].nil?
165
+ index = Qt::Native.qobject_connect_signal(handle, signal_key)
166
+ raise ArgumentError, "failed to connect signal #{signal_key.inspect} (code=#{index})" if index.negative?
167
+
168
+ registration[:index] = index
169
+ end
170
+ registration[:refcount] += 1
171
+ registration[:index]
172
+ end
173
+
174
+ def release_signal_registration(handle, signal_key)
175
+ return if @signal_registrations.nil?
176
+
177
+ per_widget = @signal_registrations[handle.address]
178
+ return if per_widget.nil?
179
+
180
+ registration = per_widget[signal_key]
181
+ return if registration.nil?
182
+
183
+ registration[:refcount] -= 1
184
+ return if registration[:refcount].positive?
185
+
186
+ Qt::Native.qobject_disconnect_signal(handle, signal_key)
187
+ per_widget.delete(signal_key)
188
+ @signal_registrations.delete(handle.address) if per_widget.empty?
189
+ end
190
+
191
+ def clear_signal_registrations_for_address(address)
192
+ @signal_handlers&.delete(address)
193
+ @internal_signal_handlers&.delete(address)
194
+ @signal_registrations&.delete(address)
195
+ end
144
196
  end
145
197
  end
@@ -26,8 +26,15 @@ module Qt
26
26
  EVENT_RESULT_CONTINUE
27
27
  end
28
28
 
29
- def dispatch_signal(signal_handlers, object_handle, signal_index, payload)
30
- return unless object_handle && signal_handlers
29
+ def dispatch_signal(internal_signal_handlers, signal_handlers, object_handle, signal_index, payload)
30
+ return unless object_handle
31
+
32
+ dispatch_signal_store(signal_handlers, object_handle, signal_index, payload)
33
+ dispatch_signal_store(internal_signal_handlers, object_handle, signal_index, payload)
34
+ end
35
+
36
+ def dispatch_signal_store(signal_handlers, object_handle, signal_index, payload)
37
+ return unless signal_handlers
31
38
 
32
39
  per_widget = signal_handlers[object_handle.address]
33
40
  return unless per_widget
@@ -36,7 +43,7 @@ module Qt
36
43
  next unless entry[:index] == signal_index
37
44
 
38
45
  typed_payload = Qt::DateTimeCodec.decode_for_signal(signal_name, payload)
39
- entry[:blocks].each { |handler| handler.call(typed_payload) }
46
+ entry[:blocks].dup.each { |handler| handler.call(typed_payload) }
40
47
  end
41
48
  end
42
49
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qt
4
+ module GeneratedSetterAliasesRuntime
5
+ module_function
6
+
7
+ def apply!(qt_module)
8
+ qt_module.constants(false).each do |const_name|
9
+ klass = qt_module.const_get(const_name, false)
10
+ next unless klass.is_a?(Class)
11
+
12
+ apply_instance_aliases!(klass)
13
+ apply_singleton_aliases!(klass)
14
+ end
15
+ end
16
+
17
+ def apply_instance_aliases!(klass)
18
+ apply_aliases_to_target!(klass, klass, :QT_API_SETTER_ALIASES)
19
+ end
20
+
21
+ def apply_singleton_aliases!(klass)
22
+ apply_aliases_to_target!(klass.singleton_class, klass, :QT_API_SINGLETON_SETTER_ALIASES)
23
+ end
24
+
25
+ def apply_aliases_to_target!(target, owner, constant_name)
26
+ return unless owner.const_defined?(constant_name, false)
27
+
28
+ owner.const_get(constant_name, false).each do |alias_name, setter_name|
29
+ next if target.method_defined?(alias_name) || target.private_method_defined?(alias_name)
30
+
31
+ target.send(:define_method, alias_name) do |value|
32
+ public_send(setter_name, value)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qt
4
+ module GeneratedSingletonForwardersRuntime
5
+ module_function
6
+
7
+ def apply!(qt_module)
8
+ qt_module.constants(false).each do |const_name|
9
+ klass = qt_module.const_get(const_name, false)
10
+ next unless klass.is_a?(Class)
11
+ next unless klass.const_defined?(:QT_API_SINGLETON_FORWARDERS, false)
12
+
13
+ klass.const_get(:QT_API_SINGLETON_FORWARDERS, false).each do |method_name|
14
+ next if klass.instance_methods(false).include?(method_name.to_sym)
15
+ next if klass.private_instance_methods(false).include?(method_name.to_sym)
16
+
17
+ klass.send(:define_method, method_name) do |*args, &block|
18
+ self.class.public_send(method_name, *args, &block)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Qt
6
+ # Decodes QObjectList bridge payloads into canonical Ruby wrappers.
7
+ module ObjectListCodec
8
+ module_function
9
+
10
+ def decode(payload, expected_qt_class = 'QObject')
11
+ raw = Qt::StringCodec.from_qt_text(payload.to_s)
12
+ return [] if raw.empty?
13
+
14
+ JSON.parse(raw).filter_map do |address|
15
+ next if address.nil? || address.to_s.empty?
16
+
17
+ Qt::ObjectWrapper.wrap(FFI::Pointer.new(Integer(address, 10)), expected_qt_class)
18
+ end
19
+ rescue JSON::ParserError, ArgumentError
20
+ []
21
+ end
22
+ end
23
+ end
@@ -5,14 +5,24 @@ module Qt
5
5
  module ObjectWrapper
6
6
  module_function
7
7
 
8
+ module ConstructorCacheHook
9
+ def initialize(*args, &block)
10
+ super
11
+ Qt::ObjectWrapper.register_wrapper(self)
12
+ end
13
+ end
14
+
8
15
  def wrap(pointer, expected_qt_class = nil)
9
16
  return nil if null_pointer?(pointer)
10
17
  return pointer if pointer.respond_to?(:handle)
11
18
 
19
+ cached = cached_wrapper_for(pointer)
20
+ return cached if cached
21
+
12
22
  klass = resolve_wrapper_class(pointer, expected_qt_class) || fallback_wrapper_class(expected_qt_class)
13
23
  return pointer unless klass
14
24
 
15
- instantiate_wrapper(klass, pointer)
25
+ register_wrapper(instantiate_wrapper(klass, pointer))
16
26
  end
17
27
 
18
28
  def null_pointer?(pointer)
@@ -26,9 +36,10 @@ module Qt
26
36
  end
27
37
 
28
38
  def candidate_wrapper_classes(expected_qt_class)
39
+ normalized_qt_class = normalize_expected_qt_class(expected_qt_class)
29
40
  @candidate_wrapper_classes ||= {}
30
- @candidate_wrapper_classes[expected_qt_class] ||= begin
31
- base = fallback_wrapper_class(expected_qt_class)
41
+ @candidate_wrapper_classes[normalized_qt_class] ||= begin
42
+ base = fallback_wrapper_class(normalized_qt_class)
32
43
  wrappers = qobject_wrapper_classes
33
44
  wrappers = wrappers.select { |klass| klass <= base } if base
34
45
  wrappers.sort_by { |klass| -inheritance_depth(klass) }
@@ -47,23 +58,95 @@ module Qt
47
58
  end
48
59
 
49
60
  def fallback_wrapper_class(expected_qt_class)
50
- return nil if expected_qt_class.nil? || !Qt.const_defined?(expected_qt_class, false)
61
+ normalized_qt_class = normalize_expected_qt_class(expected_qt_class)
62
+ return nil unless normalized_qt_class
63
+ return nil unless Qt.const_defined?(normalized_qt_class, false)
51
64
 
52
- klass = Qt.const_get(expected_qt_class, false)
65
+ klass = Qt.const_get(normalized_qt_class, false)
53
66
  return nil unless klass.is_a?(Class)
54
67
  return nil unless klass.const_defined?(:QT_CLASS, false)
55
68
  return nil unless klass <= Qt::QObject
56
69
 
57
70
  klass
71
+ rescue NameError
72
+ nil
58
73
  end
59
74
 
60
75
  def instantiate_wrapper(klass, pointer)
61
76
  wrapped = klass.allocate
62
77
  wrapped.instance_variable_set(:@handle, pointer)
63
- wrapped.init_children_tracking! if wrapped.respond_to?(:init_children_tracking!, true)
64
78
  wrapped
65
79
  end
66
80
 
81
+ def cached_wrapper_for(pointer)
82
+ wrapper_cache[pointer.address]
83
+ end
84
+
85
+ def register_wrapper(wrapper)
86
+ return wrapper unless wrapper.respond_to?(:handle)
87
+
88
+ pointer = wrapper.handle
89
+ return wrapper if null_pointer?(pointer)
90
+
91
+ cached = cached_wrapper_for(pointer)
92
+ return cached if cached
93
+
94
+ cache_wrapper(wrapper)
95
+ end
96
+
97
+ def cache_wrapper(wrapper)
98
+ pointer = wrapper.handle
99
+ wrapper_cache[pointer.address] = wrapper
100
+ ensure_destroy_hook(wrapper, pointer)
101
+ wrapper
102
+ end
103
+
104
+ def invalidate_cached_wrapper(pointer_or_address, expected_wrapper = nil)
105
+ address = pointer_or_address.is_a?(Integer) ? pointer_or_address : pointer_or_address.address
106
+ cached = wrapper_cache[address]
107
+ return unless cached
108
+ return if expected_wrapper && !cached.equal?(expected_wrapper)
109
+
110
+ wrapper_cache.delete(address)
111
+ end
112
+
113
+ def reset_cache!
114
+ @wrapper_cache = {}
115
+ @destroy_hook_addresses = {}
116
+ end
117
+
118
+ def install_constructor_cache_hooks!
119
+ qobject_wrapper_classes.each do |klass|
120
+ next if klass.instance_variable_defined?(:@__qt_object_wrapper_constructor_hook_installed)
121
+
122
+ klass.prepend(ConstructorCacheHook)
123
+ klass.instance_variable_set(:@__qt_object_wrapper_constructor_hook_installed, true)
124
+ end
125
+ end
126
+
127
+ def wrapper_cache
128
+ @wrapper_cache ||= {}
129
+ end
130
+
131
+ def destroy_hook_addresses
132
+ @destroy_hook_addresses ||= {}
133
+ end
134
+
135
+ def ensure_destroy_hook(wrapper, pointer)
136
+ address = pointer.address
137
+ return if destroy_hook_addresses[address]
138
+
139
+ destroy_hook_addresses[address] = true
140
+ Qt::EventRuntime.on_internal_signal(pointer, 'destroyed()') do |_payload|
141
+ destroy_hook_addresses.delete(address)
142
+ Qt::EventRuntime.clear_signal_registrations_for_address(address)
143
+ invalidate_cached_wrapper(address, wrapper)
144
+ end
145
+ rescue StandardError
146
+ destroy_hook_addresses.delete(address)
147
+ raise
148
+ end
149
+
67
150
  def inheritance_depth(klass)
68
151
  depth = 0
69
152
  current = klass
@@ -73,5 +156,17 @@ module Qt
73
156
  end
74
157
  depth
75
158
  end
159
+
160
+ def normalize_expected_qt_class(expected_qt_class)
161
+ return nil if expected_qt_class.nil?
162
+
163
+ value = expected_qt_class.to_s.strip
164
+ return nil if value.empty?
165
+
166
+ value = value.delete_prefix('Qt::')
167
+ return nil unless value.match?(/\A[A-Z]\w*\z/)
168
+
169
+ value
170
+ end
76
171
  end
77
172
  end
data/lib/qt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Qt
4
- VERSION = '0.1.6'
4
+ VERSION = '0.1.7'
5
5
  end
data/lib/qt.rb CHANGED
@@ -22,18 +22,23 @@ require_relative 'qt/constants'
22
22
  require_relative 'qt/string_codec'
23
23
  require_relative 'qt/date_time_codec'
24
24
  require_relative 'qt/key_sequence_codec'
25
+ require_relative 'qt/object_list_codec'
25
26
  require_relative 'qt/variant_codec'
26
27
  require_relative 'qt/inspectable'
27
- require_relative 'qt/children_tracking'
28
28
  require_relative 'qt/object_wrapper'
29
29
  require_relative 'qt/application_lifecycle'
30
30
  require_relative 'qt/bridge'
31
31
  require_relative 'qt/native'
32
+ require_relative 'qt/generated_setter_aliases_runtime'
33
+ require_relative 'qt/generated_singleton_forwarders_runtime'
32
34
  require_relative 'qt/event_runtime_dispatch'
33
35
  require_relative 'qt/event_runtime_qobject_methods'
34
36
  require_relative 'qt/event_runtime'
35
37
  require GENERATED_EVENT_PAYLOADS
36
38
  require GENERATED_WIDGETS
39
+ Qt::GeneratedSetterAliasesRuntime.apply!(Qt)
40
+ Qt::GeneratedSingletonForwardersRuntime.apply!(Qt)
41
+ Qt::ObjectWrapper.install_constructor_cache_hooks!
37
42
  require_relative 'qt/shortcut_compat'
38
43
  Qt::GeneratedConstantsRuntime.apply_generated_scoped_constants!(Qt)
39
44
 
@@ -81,6 +81,7 @@ def map_scalar_cpp_return_type(type)
81
81
  return { ffi_return: :int } if type == 'int'
82
82
  return { ffi_return: :bool } if type == 'bool'
83
83
  return { ffi_return: :string, return_cast: :qstring_to_utf8 } if type == 'QString'
84
+ return { ffi_return: :string, return_cast: :qobject_list_to_wrapped_array, object_list_class: 'QObject' } if type == 'QObjectList'
84
85
  return { ffi_return: :string, return_cast: :qdatetime_to_utf8 } if type == 'QDateTime'
85
86
  return { ffi_return: :string, return_cast: :qdate_to_utf8 } if type == 'QDate'
86
87
  return { ffi_return: :string, return_cast: :qtime_to_utf8 } if type == 'QTime'
@@ -11,6 +11,7 @@ class CppMethodReturnEmitter
11
11
  def emit
12
12
  return emit_void if method[:ffi_return] == :void
13
13
  return emit_qstring if qstring_return?
14
+ return emit_qobject_list if qobject_list_return?
14
15
  return emit_qdatetime if qdatetime_return?
15
16
  return emit_qdate if qdate_return?
16
17
  return emit_qtime if qtime_return?
@@ -32,6 +33,10 @@ class CppMethodReturnEmitter
32
33
  method[:ffi_return] == :string && method[:return_cast] == :qvariant_to_utf8
33
34
  end
34
35
 
36
+ def qobject_list_return?
37
+ method[:ffi_return] == :string && method[:return_cast] == :qobject_list_to_wrapped_array
38
+ end
39
+
35
40
  def qdatetime_return?
36
41
  method[:ffi_return] == :string && method[:return_cast] == :qdatetime_to_utf8
37
42
  end
@@ -62,6 +67,13 @@ class CppMethodReturnEmitter
62
67
  lines << ' return utf8.constData();'
63
68
  end
64
69
 
70
+ def emit_qobject_list
71
+ lines << " const QObjectList value = #{invocation};"
72
+ lines << ' thread_local QByteArray utf8;'
73
+ lines << ' utf8 = qobject_list_to_bridge_string(value).toUtf8();'
74
+ lines << ' return utf8.constData();'
75
+ end
76
+
65
77
  def emit_qdatetime
66
78
  lines << " const QDateTime value = #{invocation};"
67
79
  lines << ' thread_local QByteArray utf8;'
@@ -549,6 +549,14 @@ def cpp_bridge_prelude
549
549
  const QByteArray fallback = value.toString().toUtf8().toBase64();
550
550
  return QStringLiteral("qtv:str:") + QString::fromUtf8(fallback);
551
551
  }
552
+
553
+ QString qobject_list_to_bridge_string(const QObjectList& value) {
554
+ QJsonArray array;
555
+ for (QObject* object : value) {
556
+ array.append(QString::number(reinterpret_cast<quintptr>(object)));
557
+ }
558
+ return QString::fromUtf8(QJsonDocument(array).toJson(QJsonDocument::Compact));
559
+ }
552
560
  } // namespace
553
561
  CPP
554
562
  end
@@ -590,14 +598,19 @@ def ruby_api_metadata(methods)
590
598
  ruby_method_names = methods.flat_map do |method|
591
599
  ruby_name = ruby_safe_method_name(method[:ruby_name])
592
600
  snake = to_snake(ruby_name)
593
- snake == ruby_name ? [ruby_name] : [ruby_name, snake]
601
+ names = snake == ruby_name ? [ruby_name] : [ruby_name, snake]
602
+ names.concat(setter_alias_method_names(method))
603
+ names
594
604
  end.uniq
595
605
  properties = methods.filter_map { |method| method[:property] }.uniq
596
606
 
597
607
  {
598
608
  qt_method_names: qt_method_names,
599
609
  ruby_method_names: ruby_method_names,
600
- properties: properties
610
+ properties: properties,
611
+ setter_aliases: {},
612
+ singleton_setter_aliases: {},
613
+ singleton_forwarders: []
601
614
  }
602
615
  end
603
616
 
@@ -606,6 +619,9 @@ def append_ruby_class_api_constants(lines, qt_class:, metadata:, indent:)
606
619
  lines << "#{indent}QT_API_QT_METHODS = #{metadata[:qt_method_names].inspect}.freeze"
607
620
  lines << "#{indent}QT_API_RUBY_METHODS = #{metadata[:ruby_method_names].map(&:to_sym).inspect}.freeze"
608
621
  lines << "#{indent}QT_API_PROPERTIES = #{metadata[:properties].map(&:to_sym).inspect}.freeze"
622
+ lines << "#{indent}QT_API_SETTER_ALIASES = #{metadata[:setter_aliases].inspect}.freeze"
623
+ lines << "#{indent}QT_API_SINGLETON_SETTER_ALIASES = #{metadata[:singleton_setter_aliases].inspect}.freeze"
624
+ lines << "#{indent}QT_API_SINGLETON_FORWARDERS = #{metadata[:singleton_forwarders].inspect}.freeze"
609
625
  end
610
626
 
611
627
  def ruby_method_arguments(method, arg_map, required_arg_count)
@@ -675,6 +691,9 @@ end
675
691
 
676
692
  def ruby_native_method_body(method, rewritten_native_call)
677
693
  return "Qt::StringCodec.from_qt_text(#{rewritten_native_call})" if method[:return_cast] == :qstring_to_utf8
694
+ if method[:return_cast] == :qobject_list_to_wrapped_array
695
+ return "Qt::ObjectListCodec.decode(#{rewritten_native_call}, '#{method[:object_list_class]}')"
696
+ end
678
697
  return "Qt::VariantCodec.decode(#{rewritten_native_call})" if method[:return_cast] == :qvariant_to_utf8
679
698
  return "Qt::DateTimeCodec.decode_qdatetime(#{rewritten_native_call})" if method[:return_cast] == :qdatetime_to_utf8
680
699
  return "Qt::DateTimeCodec.decode_qdate(#{rewritten_native_call})" if method[:return_cast] == :qdate_to_utf8
@@ -694,6 +713,39 @@ def append_ruby_property_writer(lines, method:, indent:)
694
713
  lines << "#{indent}alias_method :#{snake_property}=, :#{method[:property]}=" if snake_property != method[:property]
695
714
  end
696
715
 
716
+ def setter_alias_base_name(method)
717
+ return nil unless method[:args].length == 1
718
+ return nil if method[:property]
719
+
720
+ property_name_from_setter(method[:qt_name] || method[:ruby_name].to_s)
721
+ end
722
+
723
+ def setter_alias_method_names(method)
724
+ setter_alias_specs(method).keys
725
+ end
726
+
727
+ def setter_alias_specs(method)
728
+ base_name = setter_alias_base_name(method)
729
+ return {} unless base_name
730
+
731
+ ruby_target = ruby_public_method_name(base_name)
732
+ snake_target = to_snake(ruby_target)
733
+ setter_method_name = ruby_safe_method_name(method[:ruby_name])
734
+ aliases = { "#{ruby_target}=" => setter_method_name }
735
+ aliases["#{snake_target}="] = setter_method_name if snake_target != ruby_target
736
+ aliases
737
+ end
738
+
739
+ def qapplication_singleton_forwarder_names(methods, singleton_setter_aliases)
740
+ method_names = methods.flat_map do |method|
741
+ ruby_name = ruby_safe_method_name(method[:ruby_name])
742
+ snake_alias = to_snake(ruby_name)
743
+ snake_alias == ruby_name ? [ruby_name] : [ruby_name, snake_alias]
744
+ end
745
+
746
+ (method_names + singleton_setter_aliases.keys).uniq
747
+ end
748
+
697
749
  def append_widget_initializer(lines, spec:, widget_root:, indent:)
698
750
  if spec[:constructor][:mode] == :string_path
699
751
  append_string_path_initializer(lines, spec, indent)
@@ -712,7 +764,6 @@ end
712
764
  def append_parent_widget_initializer(lines, spec, widget_root, indent)
713
765
  lines << "#{indent}def initialize(parent = nil)"
714
766
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(parent&.handle)"
715
- lines << "#{indent} init_children_tracking!" if widget_root
716
767
  append_parent_registration_logic(lines, spec, indent)
717
768
  end
718
769
 
@@ -733,20 +784,15 @@ def append_keysequence_parent_initializer(lines, spec, widget_root, indent)
733
784
  lines << "#{indent} key = nil"
734
785
  lines << "#{indent} end"
735
786
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(Qt::KeySequenceCodec.encode(key), parent&.handle)"
736
- lines << "#{indent} init_children_tracking!" if widget_root
737
787
  append_parent_registration_logic(lines, spec, indent)
738
788
  end
739
789
 
740
790
  def append_parent_registration_logic(lines, spec, indent)
741
791
  if spec[:ruby_class] == 'QWidget'
742
- lines << "#{indent} if parent"
743
- lines << "#{indent} parent.add_child(self)"
744
- lines << "#{indent} else"
792
+ lines << "#{indent} unless parent"
745
793
  lines << "#{indent} app = QApplication.current"
746
794
  lines << "#{indent} app&.register_window(self)"
747
795
  lines << "#{indent} end"
748
- elsif spec[:constructor][:register_in_parent]
749
- lines << "#{indent} parent.add_child(self) if parent&.respond_to?(:add_child)"
750
796
  end
751
797
  end
752
798
 
@@ -773,6 +819,13 @@ end
773
819
 
774
820
  def generate_ruby_qapplication(lines, spec)
775
821
  metadata = ruby_api_metadata(spec[:methods])
822
+ metadata[:singleton_setter_aliases] = Array(spec[:class_methods]).each_with_object({}) do |method, aliases|
823
+ aliases.merge!(setter_alias_specs(method))
824
+ end
825
+ metadata[:singleton_forwarders] = qapplication_singleton_forwarder_names(
826
+ Array(spec[:class_methods]),
827
+ metadata[:singleton_setter_aliases]
828
+ )
776
829
 
777
830
  append_ruby_qapplication_prelude(lines, spec, metadata)
778
831
  append_ruby_qapplication_singleton_accessors(lines)
@@ -826,6 +879,9 @@ end
826
879
 
827
880
  def qapplication_class_method_body(method, native_call)
828
881
  return " Qt::StringCodec.from_qt_text(#{native_call})" if method[:return_cast] == :qstring_to_utf8
882
+ if method[:return_cast] == :qobject_list_to_wrapped_array
883
+ return " Qt::ObjectListCodec.decode(#{native_call}, '#{method[:object_list_class]}')"
884
+ end
829
885
  return " Qt::DateTimeCodec.decode_qdatetime(#{native_call})" if method[:return_cast] == :qdatetime_to_utf8
830
886
  return " Qt::DateTimeCodec.decode_qdate(#{native_call})" if method[:return_cast] == :qdate_to_utf8
831
887
  return " Qt::DateTimeCodec.decode_qtime(#{native_call})" if method[:return_cast] == :qtime_to_utf8
@@ -842,9 +898,7 @@ def generate_ruby_widget_class_header(lines, spec, metadata:, super_ruby:, class
842
898
  append_ruby_class_api_constants(lines, qt_class: spec[:qt_class], metadata: metadata, indent: ' ')
843
899
  lines << ''
844
900
  lines << ' attr_reader :handle'
845
- lines << ' attr_reader :children' if widget_root
846
901
  lines << ' include Inspectable'
847
- lines << ' include ChildrenTracking' if widget_root
848
902
  lines << ' include EventRuntime::QObjectMethods' if qobject_based
849
903
  lines << ''
850
904
  end
@@ -883,7 +937,11 @@ end
883
937
  def ruby_api_metadata_for_spec(spec, specs_by_qt, super_qt_by_qt)
884
938
  inherited_methods = inherited_methods_for_spec(spec, specs_by_qt, super_qt_by_qt)
885
939
  all_methods = (inherited_methods + spec[:methods]).uniq { |method| method[:qt_name] }
886
- ruby_api_metadata(all_methods)
940
+ metadata = ruby_api_metadata(all_methods)
941
+ metadata[:setter_aliases] = all_methods.each_with_object({}) do |method, aliases|
942
+ aliases.merge!(setter_alias_specs(method))
943
+ end
944
+ metadata
887
945
  end
888
946
 
889
947
  def ruby_super_class_for_spec(spec, super_qt_by_qt, qt_to_ruby)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maksim Veynberg
@@ -50,7 +50,6 @@ files:
50
50
  - lib/qt.rb
51
51
  - lib/qt/application_lifecycle.rb
52
52
  - lib/qt/bridge.rb
53
- - lib/qt/children_tracking.rb
54
53
  - lib/qt/constants.rb
55
54
  - lib/qt/date_time_codec.rb
56
55
  - lib/qt/errors.rb
@@ -58,9 +57,12 @@ files:
58
57
  - lib/qt/event_runtime_dispatch.rb
59
58
  - lib/qt/event_runtime_qobject_methods.rb
60
59
  - lib/qt/generated_constants_runtime.rb
60
+ - lib/qt/generated_setter_aliases_runtime.rb
61
+ - lib/qt/generated_singleton_forwarders_runtime.rb
61
62
  - lib/qt/inspectable.rb
62
63
  - lib/qt/key_sequence_codec.rb
63
64
  - lib/qt/native.rb
65
+ - lib/qt/object_list_codec.rb
64
66
  - lib/qt/object_wrapper.rb
65
67
  - lib/qt/shortcut_compat.rb
66
68
  - lib/qt/string_codec.rb
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Qt
4
- # Child object tracking to mirror Qt parent/child ownership in Ruby.
5
- module ChildrenTracking
6
- def init_children_tracking!
7
- @children = []
8
- end
9
-
10
- def add_child(child)
11
- @children ||= []
12
- @children << child
13
- end
14
- end
15
- end