qt 0.1.6 → 0.1.8

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: 5b47b66563f703c260222b8ac0e38f2bf27e4ee654dd3152181bb8866e93e182
4
+ data.tar.gz: 797218dab7d7ce473510b05bd500c8151503b0ebc018648c5d82a5e5abb7e8f0
5
5
  SHA512:
6
- metadata.gz: aac76dc37741b86a72be2b8b5f1d671b3f0e4fc59ad95e2958264c7a9929f3dad7d445e3bded8f46d67bc304296b0610d7f427ca19eebaec99fd41dff4d454cb
7
- data.tar.gz: 50d80970f96868a576ac445b84bbf45ea002bf8c55038b52d3e0c1f2a522d89c6cf2e2120a895b43963cc912900ab63a3520be8d91278d3a8f5a6ec08e364569
6
+ metadata.gz: 37e94ba0242411ddd9f46d6858f397f53e2d2d62e38ba7ccba9a15fc517f86291b015d82dd359dfe0eb5ce12a011ebeb379d95a23c607c1be299883d5c24ed2a
7
+ data.tar.gz: 5fc3cc069da9615f8efb690d2888f5ec7f5c02422885e070331b4e271381405a082993c705d100c9e30b6ceb5661471701769c81e5ff6ff072024fe34c7b9c15
@@ -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
- return nil unless klass <= Qt::QObject
56
68
 
57
69
  klass
70
+ rescue NameError
71
+ nil
58
72
  end
59
73
 
60
74
  def instantiate_wrapper(klass, pointer)
61
75
  wrapped = klass.allocate
62
76
  wrapped.instance_variable_set(:@handle, pointer)
63
- wrapped.init_children_tracking! if wrapped.respond_to?(:init_children_tracking!, true)
64
77
  wrapped
65
78
  end
66
79
 
80
+ def cached_wrapper_for(pointer)
81
+ wrapper_cache[pointer.address]
82
+ end
83
+
84
+ def register_wrapper(wrapper)
85
+ return wrapper unless wrapper.respond_to?(:handle)
86
+
87
+ pointer = wrapper.handle
88
+ return wrapper if null_pointer?(pointer)
89
+ return wrapper unless qobject_wrapper?(wrapper.class)
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,21 @@ module Qt
73
156
  end
74
157
  depth
75
158
  end
159
+
160
+ def qobject_wrapper?(klass)
161
+ klass.is_a?(Class) && klass <= Qt::QObject
162
+ end
163
+
164
+ def normalize_expected_qt_class(expected_qt_class)
165
+ return nil if expected_qt_class.nil?
166
+
167
+ value = expected_qt_class.to_s.strip
168
+ return nil if value.empty?
169
+
170
+ value = value.delete_prefix('Qt::')
171
+ return nil unless value.match?(/\A[A-Z]\w*\z/)
172
+
173
+ value
174
+ end
76
175
  end
77
176
  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.8'
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
 
@@ -52,6 +52,7 @@ def map_cpp_arg_type(type_name, qt_class: nil, int_cast_types: nil)
52
52
  return { ffi: :string, cast: :qdate_from_utf8 } if type == 'QDate'
53
53
  return { ffi: :string, cast: :qtime_from_utf8 } if type == 'QTime'
54
54
  return { ffi: :string, cast: :qkeysequence_from_utf8 } if type == 'QKeySequence'
55
+ return { ffi: :int, cast: :qcursor_shape } if type == 'QCursor'
55
56
  return { ffi: :pointer, cast: :qicon_ref } if type == 'QIcon'
56
57
  return { ffi: :string, cast: :qany_string_view } if type == 'QAnyStringView'
57
58
  return { ffi: :string, cast: :qvariant_from_utf8 } if type == 'QVariant'
@@ -81,6 +82,7 @@ def map_scalar_cpp_return_type(type)
81
82
  return { ffi_return: :int } if type == 'int'
82
83
  return { ffi_return: :bool } if type == 'bool'
83
84
  return { ffi_return: :string, return_cast: :qstring_to_utf8 } if type == 'QString'
85
+ return { ffi_return: :string, return_cast: :qobject_list_to_wrapped_array, object_list_class: 'QObject' } if type == 'QObjectList'
84
86
  return { ffi_return: :string, return_cast: :qdatetime_to_utf8 } if type == 'QDateTime'
85
87
  return { ffi_return: :string, return_cast: :qdate_to_utf8 } if type == 'QDate'
86
88
  return { ffi_return: :string, return_cast: :qtime_to_utf8 } if type == 'QTime'
@@ -94,7 +96,7 @@ def map_pointer_cpp_return_type(type, ast: nil)
94
96
 
95
97
  info = { ffi_return: :pointer }
96
98
  base_type = type.sub(/\s*\*\z/, '').strip
97
- if ast && class_inherits?(ast, base_type, 'QObject')
99
+ if ast && ast_class_index(ast)[:methods_by_class].key?(base_type)
98
100
  info[:pointer_class] = base_type
99
101
  end
100
102
 
@@ -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;'
@@ -19,6 +19,8 @@ def all_ffi_functions(specs, free_function_specs:)
19
19
  end
20
20
 
21
21
  def append_constructor_ffi_function(fns, spec)
22
+ return if spec[:constructor][:mode] == :wrap_only
23
+
22
24
  ctor_args = constructor_ffi_args(spec)
23
25
  fns << { name: ctor_function_name(spec), ffi_return: :pointer, args: ctor_args }
24
26
  end
@@ -160,7 +160,7 @@ def related_value_class_candidate?(ast, qt_class, all_classes, template_classes)
160
160
  return false if abstract_class?(ast, qt_class)
161
161
  return false if class_inherits?(ast, qt_class, 'QObject')
162
162
 
163
- constructor_usable_for_codegen?(ast, qt_class)
163
+ true
164
164
  end
165
165
 
166
166
  def related_qt_type_names(ast, qt_class)
@@ -178,11 +178,12 @@ end
178
178
 
179
179
  def append_related_qt_types_from_decl(out, parsed, method_name)
180
180
  candidates = []
181
- candidates << normalized_cpp_type_name(parsed[:return_type]).to_s if parsed[:return_type]
182
- parsed[:params].each { |param| candidates << normalized_cpp_type_name(param[:type]).to_s }
183
- candidates.each do |candidate|
181
+ candidates << [related_qt_type_name(parsed[:return_type]), parsed[:return_type].to_s] if parsed[:return_type]
182
+ parsed[:params].each { |param| candidates << [related_qt_type_name(param[:type]), param[:type].to_s] }
183
+ candidates.each do |candidate, raw_type|
184
184
  next if candidate.empty? || !candidate.start_with?('Q')
185
- next unless related_type_name_matches_method?(candidate, method_name)
185
+ next unless related_type_name_matches_method?(candidate, method_name) ||
186
+ (qt_pointer_type?(raw_type) && candidate.end_with?('Item'))
186
187
 
187
188
  out << candidate
188
189
  end
@@ -195,6 +196,14 @@ def related_type_name_matches_method?(qt_type, method_name)
195
196
  method_name.to_s.downcase.include?(token)
196
197
  end
197
198
 
199
+ def qt_pointer_type?(raw_type)
200
+ raw_type.to_s.strip.end_with?('*')
201
+ end
202
+
203
+ def related_qt_type_name(raw_type)
204
+ normalized_cpp_type_name(raw_type).to_s.delete_suffix('*')
205
+ end
206
+
198
207
  def discover_target_qt_classes(ast, scope)
199
208
  all_classes = q_class_names(ast)
200
209
  template_classes = template_qt_classes(ast)
@@ -220,9 +229,24 @@ def widget_target_qt_class?(ast, qt_class)
220
229
 
221
230
  class_inherits?(ast, qt_class, 'QWidget') ||
222
231
  class_inherits?(ast, qt_class, 'QLayout') ||
232
+ widget_item_target_qt_class?(ast, qt_class) ||
223
233
  qt_class == 'QTableWidgetItem'
224
234
  end
225
235
 
236
+ def widget_item_target_qt_class?(ast, qt_class)
237
+ return false unless qt_class.end_with?('Item')
238
+ return false if class_inherits?(ast, qt_class, 'QObject')
239
+
240
+ collect_constructor_decls(ast, qt_class).any? do |decl|
241
+ parsed = parse_method_signature(decl)
242
+ next false unless parsed
243
+
244
+ parsed[:params].any? do |param|
245
+ normalized_cpp_type_name(param[:type]).to_s.match?(/\AQ\w*Widget\*\z/)
246
+ end
247
+ end
248
+ end
249
+
226
250
  def qobject_target_qt_class?(ast, qt_class)
227
251
  return false if qt_class.end_with?('Private')
228
252
  return false if qt_class == 'QApplication'
@@ -264,8 +288,10 @@ def build_base_spec_for_qt_class(ast, qt_class)
264
288
  parent_constructor_for_type(parent_type, widget_child)
265
289
  elsif string_path_cast
266
290
  string_path_constructor(string_path_cast)
267
- else
291
+ elsif ctor_decls.any? { |decl| constructor_supports_no_args?(decl) }
268
292
  { parent: false }
293
+ else
294
+ { parent: false, mode: :wrap_only }
269
295
  end
270
296
  base_spec_hash(qt_class, parent_ctor)
271
297
  end
@@ -246,6 +246,7 @@ def arg_expr(arg)
246
246
  when :qdate_from_utf8 then "qdate_from_bridge_value(#{arg[:name]})"
247
247
  when :qtime_from_utf8 then "qtime_from_bridge_value(#{arg[:name]})"
248
248
  when :qkeysequence_from_utf8 then "QKeySequence(as_qstring(#{arg[:name]}))"
249
+ when :qcursor_shape then "QCursor(static_cast<Qt::CursorShape>(#{arg[:name]}))"
249
250
  when :qicon_ref then "*static_cast<QIcon*>(#{arg[:name]})"
250
251
  when :qany_string_view then "QAnyStringView(as_qstring(#{arg[:name]}))"
251
252
  when :qvariant_from_utf8 then "qvariant_from_bridge_value(#{arg[:name]})"
@@ -385,8 +386,10 @@ def generate_cpp_bridge(specs, free_function_specs)
385
386
  end
386
387
 
387
388
  def append_cpp_spec_methods(lines, spec)
388
- generate_cpp_constructor(lines, spec)
389
- lines << ''
389
+ unless spec[:constructor][:mode] == :wrap_only
390
+ generate_cpp_constructor(lines, spec)
391
+ lines << ''
392
+ end
390
393
  spec[:methods].each do |method|
391
394
  generate_cpp_method(lines, spec, method)
392
395
  lines << ''
@@ -549,6 +552,14 @@ def cpp_bridge_prelude
549
552
  const QByteArray fallback = value.toString().toUtf8().toBase64();
550
553
  return QStringLiteral("qtv:str:") + QString::fromUtf8(fallback);
551
554
  }
555
+
556
+ QString qobject_list_to_bridge_string(const QObjectList& value) {
557
+ QJsonArray array;
558
+ for (QObject* object : value) {
559
+ array.append(QString::number(reinterpret_cast<quintptr>(object)));
560
+ }
561
+ return QString::fromUtf8(QJsonDocument(array).toJson(QJsonDocument::Compact));
562
+ }
552
563
  } // namespace
553
564
  CPP
554
565
  end
@@ -590,14 +601,19 @@ def ruby_api_metadata(methods)
590
601
  ruby_method_names = methods.flat_map do |method|
591
602
  ruby_name = ruby_safe_method_name(method[:ruby_name])
592
603
  snake = to_snake(ruby_name)
593
- snake == ruby_name ? [ruby_name] : [ruby_name, snake]
604
+ names = snake == ruby_name ? [ruby_name] : [ruby_name, snake]
605
+ names.concat(setter_alias_method_names(method))
606
+ names
594
607
  end.uniq
595
608
  properties = methods.filter_map { |method| method[:property] }.uniq
596
609
 
597
610
  {
598
611
  qt_method_names: qt_method_names,
599
612
  ruby_method_names: ruby_method_names,
600
- properties: properties
613
+ properties: properties,
614
+ setter_aliases: {},
615
+ singleton_setter_aliases: {},
616
+ singleton_forwarders: []
601
617
  }
602
618
  end
603
619
 
@@ -606,6 +622,9 @@ def append_ruby_class_api_constants(lines, qt_class:, metadata:, indent:)
606
622
  lines << "#{indent}QT_API_QT_METHODS = #{metadata[:qt_method_names].inspect}.freeze"
607
623
  lines << "#{indent}QT_API_RUBY_METHODS = #{metadata[:ruby_method_names].map(&:to_sym).inspect}.freeze"
608
624
  lines << "#{indent}QT_API_PROPERTIES = #{metadata[:properties].map(&:to_sym).inspect}.freeze"
625
+ lines << "#{indent}QT_API_SETTER_ALIASES = #{metadata[:setter_aliases].inspect}.freeze"
626
+ lines << "#{indent}QT_API_SINGLETON_SETTER_ALIASES = #{metadata[:singleton_setter_aliases].inspect}.freeze"
627
+ lines << "#{indent}QT_API_SINGLETON_FORWARDERS = #{metadata[:singleton_forwarders].inspect}.freeze"
609
628
  end
610
629
 
611
630
  def ruby_method_arguments(method, arg_map, required_arg_count)
@@ -675,6 +694,9 @@ end
675
694
 
676
695
  def ruby_native_method_body(method, rewritten_native_call)
677
696
  return "Qt::StringCodec.from_qt_text(#{rewritten_native_call})" if method[:return_cast] == :qstring_to_utf8
697
+ if method[:return_cast] == :qobject_list_to_wrapped_array
698
+ return "Qt::ObjectListCodec.decode(#{rewritten_native_call}, '#{method[:object_list_class]}')"
699
+ end
678
700
  return "Qt::VariantCodec.decode(#{rewritten_native_call})" if method[:return_cast] == :qvariant_to_utf8
679
701
  return "Qt::DateTimeCodec.decode_qdatetime(#{rewritten_native_call})" if method[:return_cast] == :qdatetime_to_utf8
680
702
  return "Qt::DateTimeCodec.decode_qdate(#{rewritten_native_call})" if method[:return_cast] == :qdate_to_utf8
@@ -694,8 +716,46 @@ def append_ruby_property_writer(lines, method:, indent:)
694
716
  lines << "#{indent}alias_method :#{snake_property}=, :#{method[:property]}=" if snake_property != method[:property]
695
717
  end
696
718
 
719
+ def setter_alias_base_name(method)
720
+ return nil unless method[:args].length == 1
721
+ return nil if method[:property]
722
+
723
+ return 'cursor' if method[:qt_name] == 'setCursor'
724
+
725
+ property_name_from_setter(method[:qt_name] || method[:ruby_name].to_s)
726
+ end
727
+
728
+ def setter_alias_method_names(method)
729
+ setter_alias_specs(method).keys
730
+ end
731
+
732
+ def setter_alias_specs(method)
733
+ base_name = setter_alias_base_name(method)
734
+ return {} unless base_name
735
+
736
+ ruby_target = ruby_public_method_name(base_name)
737
+ snake_target = to_snake(ruby_target)
738
+ setter_method_name = ruby_safe_method_name(method[:ruby_name])
739
+ aliases = { "#{ruby_target}=" => setter_method_name }
740
+ aliases["#{snake_target}="] = setter_method_name if snake_target != ruby_target
741
+ aliases
742
+ end
743
+
744
+ def qapplication_singleton_forwarder_names(methods, singleton_setter_aliases)
745
+ method_names = methods.flat_map do |method|
746
+ ruby_name = ruby_safe_method_name(method[:ruby_name])
747
+ snake_alias = to_snake(ruby_name)
748
+ snake_alias == ruby_name ? [ruby_name] : [ruby_name, snake_alias]
749
+ end
750
+
751
+ (method_names + singleton_setter_aliases.keys).uniq
752
+ end
753
+
697
754
  def append_widget_initializer(lines, spec:, widget_root:, indent:)
698
- if spec[:constructor][:mode] == :string_path
755
+ if spec[:constructor][:mode] == :wrap_only
756
+ append_wrap_only_initializer(lines, spec, indent)
757
+ return
758
+ elsif spec[:constructor][:mode] == :string_path
699
759
  append_string_path_initializer(lines, spec, indent)
700
760
  elsif spec[:constructor][:mode] == :keysequence_parent
701
761
  append_keysequence_parent_initializer(lines, spec, widget_root, indent)
@@ -709,10 +769,15 @@ def append_widget_initializer(lines, spec:, widget_root:, indent:)
709
769
  lines << "#{indent}end"
710
770
  end
711
771
 
772
+ def append_wrap_only_initializer(lines, spec, indent)
773
+ lines << "#{indent}def initialize(*)"
774
+ lines << "#{indent} raise NotImplementedError, '#{spec[:ruby_class]} cannot be directly instantiated yet'"
775
+ lines << "#{indent}end"
776
+ end
777
+
712
778
  def append_parent_widget_initializer(lines, spec, widget_root, indent)
713
779
  lines << "#{indent}def initialize(parent = nil)"
714
780
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(parent&.handle)"
715
- lines << "#{indent} init_children_tracking!" if widget_root
716
781
  append_parent_registration_logic(lines, spec, indent)
717
782
  end
718
783
 
@@ -733,20 +798,15 @@ def append_keysequence_parent_initializer(lines, spec, widget_root, indent)
733
798
  lines << "#{indent} key = nil"
734
799
  lines << "#{indent} end"
735
800
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(Qt::KeySequenceCodec.encode(key), parent&.handle)"
736
- lines << "#{indent} init_children_tracking!" if widget_root
737
801
  append_parent_registration_logic(lines, spec, indent)
738
802
  end
739
803
 
740
804
  def append_parent_registration_logic(lines, spec, indent)
741
805
  if spec[:ruby_class] == 'QWidget'
742
- lines << "#{indent} if parent"
743
- lines << "#{indent} parent.add_child(self)"
744
- lines << "#{indent} else"
806
+ lines << "#{indent} unless parent"
745
807
  lines << "#{indent} app = QApplication.current"
746
808
  lines << "#{indent} app&.register_window(self)"
747
809
  lines << "#{indent} end"
748
- elsif spec[:constructor][:register_in_parent]
749
- lines << "#{indent} parent.add_child(self) if parent&.respond_to?(:add_child)"
750
810
  end
751
811
  end
752
812
 
@@ -773,6 +833,13 @@ end
773
833
 
774
834
  def generate_ruby_qapplication(lines, spec)
775
835
  metadata = ruby_api_metadata(spec[:methods])
836
+ metadata[:singleton_setter_aliases] = Array(spec[:class_methods]).each_with_object({}) do |method, aliases|
837
+ aliases.merge!(setter_alias_specs(method))
838
+ end
839
+ metadata[:singleton_forwarders] = qapplication_singleton_forwarder_names(
840
+ Array(spec[:class_methods]),
841
+ metadata[:singleton_setter_aliases]
842
+ )
776
843
 
777
844
  append_ruby_qapplication_prelude(lines, spec, metadata)
778
845
  append_ruby_qapplication_singleton_accessors(lines)
@@ -826,6 +893,9 @@ end
826
893
 
827
894
  def qapplication_class_method_body(method, native_call)
828
895
  return " Qt::StringCodec.from_qt_text(#{native_call})" if method[:return_cast] == :qstring_to_utf8
896
+ if method[:return_cast] == :qobject_list_to_wrapped_array
897
+ return " Qt::ObjectListCodec.decode(#{native_call}, '#{method[:object_list_class]}')"
898
+ end
829
899
  return " Qt::DateTimeCodec.decode_qdatetime(#{native_call})" if method[:return_cast] == :qdatetime_to_utf8
830
900
  return " Qt::DateTimeCodec.decode_qdate(#{native_call})" if method[:return_cast] == :qdate_to_utf8
831
901
  return " Qt::DateTimeCodec.decode_qtime(#{native_call})" if method[:return_cast] == :qtime_to_utf8
@@ -842,9 +912,7 @@ def generate_ruby_widget_class_header(lines, spec, metadata:, super_ruby:, class
842
912
  append_ruby_class_api_constants(lines, qt_class: spec[:qt_class], metadata: metadata, indent: ' ')
843
913
  lines << ''
844
914
  lines << ' attr_reader :handle'
845
- lines << ' attr_reader :children' if widget_root
846
915
  lines << ' include Inspectable'
847
- lines << ' include ChildrenTracking' if widget_root
848
916
  lines << ' include EventRuntime::QObjectMethods' if qobject_based
849
917
  lines << ''
850
918
  end
@@ -883,7 +951,11 @@ end
883
951
  def ruby_api_metadata_for_spec(spec, specs_by_qt, super_qt_by_qt)
884
952
  inherited_methods = inherited_methods_for_spec(spec, specs_by_qt, super_qt_by_qt)
885
953
  all_methods = (inherited_methods + spec[:methods]).uniq { |method| method[:qt_name] }
886
- ruby_api_metadata(all_methods)
954
+ metadata = ruby_api_metadata(all_methods)
955
+ metadata[:setter_aliases] = all_methods.each_with_object({}) do |method, aliases|
956
+ aliases.merge!(setter_alias_specs(method))
957
+ end
958
+ metadata
887
959
  end
888
960
 
889
961
  def ruby_super_class_for_spec(spec, super_qt_by_qt, qt_to_ruby)
@@ -991,6 +1063,7 @@ def collect_enum_constants_for_scope(ast, target_scope, warnings = [])
991
1063
  next unless node['kind'] == 'EnumDecl'
992
1064
  next unless scope == target_scope
993
1065
 
1066
+ next_value = 0
994
1067
  Array(node['inner']).each do |entry|
995
1068
  next unless entry['kind'] == 'EnumConstantDecl'
996
1069
 
@@ -1000,9 +1073,14 @@ def collect_enum_constants_for_scope(ast, target_scope, warnings = [])
1000
1073
 
1001
1074
  raw_value = ast_extract_first_value(entry)
1002
1075
  value = parse_ast_integer_value(raw_value)
1003
- next if value.nil?
1076
+ if value.nil?
1077
+ value = next_value
1078
+ else
1079
+ next_value = value
1080
+ end
1004
1081
 
1005
1082
  append_constant_with_conflict_warning(constants, name, value, warnings, target_scope.join('::'))
1083
+ next_value = value + 1
1006
1084
  end
1007
1085
  end
1008
1086
 
@@ -1017,6 +1095,9 @@ def collect_qt_namespace_enum_constants(ast, warnings = [])
1017
1095
 
1018
1096
  append_constant_with_conflict_warning(constants, alias_name, value, warnings, 'Qt::QEventAlias')
1019
1097
  end
1098
+ collect_enum_constants_for_scope(ast, ['QCursor'], warnings).each do |name, value|
1099
+ append_constant_with_conflict_warning(constants, name, value, warnings, 'Qt::QCursorAlias')
1100
+ end
1020
1101
  constants
1021
1102
  end
1022
1103
 
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.8
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
@@ -98,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
100
  - !ruby/object:Gem::Version
99
101
  version: '0'
100
102
  requirements: []
101
- rubygems_version: 3.6.7
103
+ rubygems_version: 4.0.9
102
104
  specification_version: 4
103
105
  summary: Ruby bindings for Qt 6.4.2+
104
106
  test_files: []
@@ -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