qt 0.1.8 → 0.1.9

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: 5b47b66563f703c260222b8ac0e38f2bf27e4ee654dd3152181bb8866e93e182
4
- data.tar.gz: 797218dab7d7ce473510b05bd500c8151503b0ebc018648c5d82a5e5abb7e8f0
3
+ metadata.gz: 50d48e8571c76d7413f893a6189b7670629bd3a80123a131bf9cc00ffe6d818e
4
+ data.tar.gz: bcb0081e9ebabc04175f7f041fdf8442293d08191e5ee6e2b26164d32c730942
5
5
  SHA512:
6
- metadata.gz: 37e94ba0242411ddd9f46d6858f397f53e2d2d62e38ba7ccba9a15fc517f86291b015d82dd359dfe0eb5ce12a011ebeb379d95a23c607c1be299883d5c24ed2a
7
- data.tar.gz: 5fc3cc069da9615f8efb690d2888f5ec7f5c02422885e070331b4e271381405a082993c705d100c9e30b6ceb5661471701769c81e5ff6ff072024fe34c7b9c15
6
+ metadata.gz: 82bde56dd8d499bfc122923554109e6f7f92dfea399ed6fd2f821737c29cfb487b0025908409273aeb13df2ca9ed57a36eb524142a968c24a84eba43af4836b3
7
+ data.tar.gz: 0444cabcb5c8ce71559758e92168f0f357de967edaaf15671f5f867f7e115dc48615e49de6d4d9a3fa55c7d098da46460b810ec0ac07ff625922c724daeac575
data/README.md CHANGED
@@ -301,6 +301,12 @@ Native event-runtime debug logs:
301
301
  QT_RUBY_EVENT_DEBUG=1 ruby your_app.rb
302
302
  ```
303
303
 
304
+ Object-wrapper fallback debug logs:
305
+
306
+ ```bash
307
+ QT_RUBY_OBJECT_WRAPPER_DEBUG=1 ruby your_app.rb
308
+ ```
309
+
304
310
  Optional tuning:
305
311
 
306
312
  ```bash
@@ -6,6 +6,11 @@ require 'fileutils'
6
6
  PKG_CONFIG = RbConfig::CONFIG['PKG_CONFIG'] || 'pkg-config'
7
7
  QT_PACKAGES = %w[Qt6Core Qt6Gui Qt6Widgets].freeze
8
8
  MINIMUM_QT_VERSION = Gem::Version.new('6.4.2')
9
+ ROOT = File.expand_path('../..', __dir__)
10
+ GENERATOR = File.join(ROOT, 'scripts/generate_bridge.rb')
11
+ GENERATED_CPP = File.join(ROOT, 'build/generated/qt_ruby_bridge.cpp')
12
+ GENERATED_EVENT_PAYLOADS = File.join(ROOT, 'build/generated/event_payloads.inc')
13
+ LOCAL_CPP = File.expand_path('qt_ruby_bridge.cpp')
9
14
 
10
15
  def pkg_config(*)
11
16
  system(PKG_CONFIG, *, out: File::NULL, err: File::NULL)
@@ -15,15 +20,27 @@ def pkg_config_capture(*args)
15
20
  `#{[PKG_CONFIG, *args].join(' ')}`.strip
16
21
  end
17
22
 
18
- abort 'pkg-config is required to build qt-ruby bridge.' unless find_executable(PKG_CONFIG)
23
+ def generated_bridge_inputs_available?
24
+ (File.exist?(LOCAL_CPP) || File.exist?(GENERATED_CPP)) && File.exist?(GENERATED_EVENT_PAYLOADS)
25
+ end
26
+
27
+ def generator_env
28
+ scope = ENV.fetch('QT_RUBY_SCOPE', nil)
29
+ scope && !scope.empty? ? { 'QT_RUBY_SCOPE' => scope } : {}
30
+ end
31
+
32
+ def ensure_generated_bridge_inputs!
33
+ return false if generated_bridge_inputs_available?
19
34
 
20
- generator = File.expand_path('../../scripts/generate_bridge.rb', __dir__)
21
- abort "Generator script not found: #{generator}" unless File.exist?(generator)
35
+ abort "Generator script not found: #{GENERATOR}" unless File.exist?(GENERATOR)
36
+ abort 'Failed to generate Qt bridge files.' unless system(generator_env, RbConfig.ruby, GENERATOR)
37
+
38
+ true
39
+ end
40
+
41
+ abort 'pkg-config is required to build qt-ruby bridge.' unless find_executable(PKG_CONFIG)
22
42
 
23
- generator_env = {}
24
- scope = ENV.fetch('QT_RUBY_SCOPE', nil)
25
- generator_env['QT_RUBY_SCOPE'] = scope if scope && !scope.empty?
26
- abort 'Failed to generate Qt bridge files.' unless system(generator_env, RbConfig.ruby, generator)
43
+ generated_by_extconf = ensure_generated_bridge_inputs!
27
44
 
28
45
  missing = QT_PACKAGES.reject { |pkg| pkg_config('--exists', pkg) }
29
46
  abort "Missing Qt packages: #{missing.join(', ')}" unless missing.empty?
@@ -34,10 +51,10 @@ abort "Qt version #{qt_version} is too old. Require >= #{MINIMUM_QT_VERSION}." i
34
51
 
35
52
  cflags = pkg_config_capture('--cflags', *QT_PACKAGES)
36
53
  libs = pkg_config_capture('--libs', *QT_PACKAGES)
37
- generated_cpp = if File.exist?('qt_ruby_bridge.cpp')
38
- File.expand_path('qt_ruby_bridge.cpp')
54
+ generated_cpp = if !generated_by_extconf && File.exist?(LOCAL_CPP)
55
+ LOCAL_CPP
39
56
  else
40
- File.expand_path('../../build/generated/qt_ruby_bridge.cpp', __dir__)
57
+ GENERATED_CPP
41
58
  end
42
59
  runtime_hpp = File.expand_path('../../ext/qt_ruby_bridge/qt_ruby_runtime.hpp', __dir__)
43
60
  runtime_cpp_files = %w[
@@ -52,8 +69,7 @@ abort "Runtime header not found: #{runtime_hpp}" unless File.exist?(runtime_hpp)
52
69
  missing_runtime = runtime_cpp_files.reject { |path| File.exist?(path) }
53
70
  abort "Runtime source not found: #{missing_runtime.join(', ')}" unless missing_runtime.empty?
54
71
 
55
- local_cpp = File.expand_path('qt_ruby_bridge.cpp')
56
- FileUtils.cp(generated_cpp, local_cpp) unless File.exist?(local_cpp) && File.identical?(generated_cpp, local_cpp)
72
+ FileUtils.cp(generated_cpp, LOCAL_CPP) unless File.exist?(LOCAL_CPP) && File.identical?(generated_cpp, LOCAL_CPP)
57
73
  runtime_cpp_files.each do |runtime_cpp|
58
74
  local_runtime_cpp = File.expand_path(File.basename(runtime_cpp))
59
75
  unless File.exist?(local_runtime_cpp) && File.identical?(runtime_cpp, local_runtime_cpp)
@@ -20,7 +20,10 @@ module Qt
20
20
  return cached if cached
21
21
 
22
22
  klass = resolve_wrapper_class(pointer, expected_qt_class) || fallback_wrapper_class(expected_qt_class)
23
- return pointer unless klass
23
+ unless klass
24
+ warn_unwrapped_pointer(pointer, expected_qt_class)
25
+ return pointer
26
+ end
24
27
 
25
28
  register_wrapper(instantiate_wrapper(klass, pointer))
26
29
  end
@@ -113,6 +116,7 @@ module Qt
113
116
  def reset_cache!
114
117
  @wrapper_cache = {}
115
118
  @destroy_hook_addresses = {}
119
+ @unwrapped_pointer_warning_keys = {}
116
120
  end
117
121
 
118
122
  def install_constructor_cache_hooks!
@@ -132,6 +136,33 @@ module Qt
132
136
  @destroy_hook_addresses ||= {}
133
137
  end
134
138
 
139
+ def object_wrapper_debug_enabled?
140
+ ENV['QT_RUBY_OBJECT_WRAPPER_DEBUG'] == '1'
141
+ end
142
+
143
+ def warn_unwrapped_pointer(pointer, expected_qt_class)
144
+ return unless object_wrapper_debug_enabled?
145
+
146
+ label = warning_qt_class_label(expected_qt_class)
147
+ return if unwrapped_pointer_warning_keys[label]
148
+
149
+ unwrapped_pointer_warning_keys[label] = true
150
+ warn "[qt-ruby-wrapper] returning raw pointer for #{label}; no generated wrapper class matched " \
151
+ "address=0x#{pointer.address.to_s(16)}"
152
+ end
153
+
154
+ def warning_qt_class_label(expected_qt_class)
155
+ normalized = normalize_expected_qt_class(expected_qt_class)
156
+ return normalized if normalized
157
+
158
+ raw = expected_qt_class.to_s.strip
159
+ raw.empty? ? '(unknown Qt class)' : raw
160
+ end
161
+
162
+ def unwrapped_pointer_warning_keys
163
+ @unwrapped_pointer_warning_keys ||= {}
164
+ end
165
+
135
166
  def ensure_destroy_hook(wrapper, pointer)
136
167
  address = pointer.address
137
168
  return if destroy_hook_addresses[address]
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.8'
4
+ VERSION = '0.1.9'
5
5
  end
@@ -109,6 +109,10 @@ def ctor_function_name(spec)
109
109
  "qt_ruby_#{spec[:prefix]}_new"
110
110
  end
111
111
 
112
- def method_function_name(spec, method)
112
+ def default_method_function_name(spec, method)
113
113
  "qt_ruby_#{spec[:prefix]}_#{to_snake(method[:qt_name])}"
114
114
  end
115
+
116
+ def method_function_name(spec, method)
117
+ method[:native_name] || default_method_function_name(spec, method)
118
+ end
@@ -151,6 +151,90 @@ def discover_related_value_classes(ast, seed_classes, all_classes, template_clas
151
151
  discovered.to_a
152
152
  end
153
153
 
154
+ def discover_related_qobject_wrapper_classes(ast, seed_classes, all_classes, template_classes, scope)
155
+ discovered = seed_classes.to_set
156
+ queue = seed_classes.dup
157
+ related = []
158
+
159
+ until queue.empty?
160
+ klass = queue.shift
161
+ related_qobject_pointer_type_names(ast, klass).each do |candidate|
162
+ next if discovered.include?(candidate)
163
+ next unless related_qobject_wrapper_candidate?(ast, candidate, all_classes, template_classes, scope)
164
+
165
+ discovered << candidate
166
+ related << candidate
167
+ queue << candidate
168
+ end
169
+ end
170
+
171
+ related
172
+ end
173
+
174
+ def related_qobject_wrapper_candidate?(ast, qt_class, all_classes, template_classes, scope)
175
+ return false unless all_classes.include?(qt_class)
176
+ return false if template_classes.include?(qt_class)
177
+ return false if qt_class.end_with?('Private')
178
+ return false if qt_class == 'QApplication'
179
+ return false unless class_inherits?(ast, qt_class, 'QObject')
180
+
181
+ related_qobject_wrapper_matches_scope?(ast, qt_class, scope)
182
+ end
183
+
184
+ def related_qobject_wrapper_matches_scope?(ast, qt_class, scope)
185
+ case scope
186
+ when 'widgets'
187
+ class_inherits?(ast, qt_class, 'QWidget') || class_inherits?(ast, qt_class, 'QLayout')
188
+ when 'qobject'
189
+ !class_inherits?(ast, qt_class, 'QWidget') && !class_inherits?(ast, qt_class, 'QLayout')
190
+ when 'all'
191
+ true
192
+ else
193
+ raise "Unsupported QT_RUBY_SCOPE=#{scope.inspect}. Supported: #{SUPPORTED_SCOPES.join(', ')}"
194
+ end
195
+ end
196
+
197
+ def related_qobject_pointer_type_names(ast, qt_class)
198
+ int_cast_types = ast_int_cast_type_set(ast)
199
+ method_decls_for_related_wrapper_scan(ast, qt_class).each_with_object(Set.new) do |decl, out|
200
+ next unless auto_method_decl_candidate?(decl)
201
+ next unless auto_method_supported_for_related_wrapper_scan?(ast, decl, qt_class, int_cast_types)
202
+
203
+ parsed = parse_method_signature(decl)
204
+ append_qobject_pointer_types_from_decl(out, parsed)
205
+ end
206
+ end
207
+
208
+ def method_decls_for_related_wrapper_scan(ast, qt_class)
209
+ collect_method_names_with_bases(ast, qt_class).flat_map do |method_name|
210
+ collect_method_decls_with_bases(ast, qt_class, method_name)
211
+ end
212
+ end
213
+
214
+ def auto_method_supported_for_related_wrapper_scan?(ast, decl, qt_class, int_cast_types)
215
+ entry = { qt_name: decl['name'] }
216
+ !build_auto_method_from_decl(decl, entry, qt_class: qt_class, int_cast_types: int_cast_types, ast: ast).nil?
217
+ end
218
+
219
+ def append_qobject_pointer_types_from_decl(out, parsed)
220
+ pointer_type = qobject_pointer_type_name(parsed[:return_type])
221
+ out << pointer_type if pointer_type
222
+ parsed[:params].each do |param|
223
+ pointer_type = qobject_pointer_type_name(param[:type])
224
+ out << pointer_type if pointer_type
225
+ end
226
+ end
227
+
228
+ def qobject_pointer_type_name(raw_type)
229
+ normalized = normalized_cpp_type_name(raw_type).to_s
230
+ return nil unless normalized.end_with?('*')
231
+
232
+ type_name = normalized.delete_suffix('*')
233
+ return nil unless type_name.match?(/\AQ[A-Z]\w*\z/)
234
+
235
+ type_name
236
+ end
237
+
154
238
  def related_value_class_candidate?(ast, qt_class, all_classes, template_classes)
155
239
  return false if SCALAR_BRIDGED_QT_TYPES.include?(qt_class)
156
240
  return false unless all_classes.include?(qt_class)
@@ -215,9 +299,13 @@ def discover_target_qt_classes(ast, scope)
215
299
  targets = timed("discover_target_qt_classes/#{scope}/related") do
216
300
  discover_related_value_classes(ast, base_targets, all_classes, template_classes).sort
217
301
  end
302
+ wrapper_targets = timed("discover_target_qt_classes/#{scope}/related_qobject_wrappers") do
303
+ discover_related_qobject_wrapper_classes(ast, targets, all_classes, template_classes, scope).sort
304
+ end
305
+ targets = (targets + wrapper_targets).uniq.sort
218
306
  debug_log(
219
307
  "discover_target_qt_classes scope=#{scope} total_q=#{all_classes.length} " \
220
- "base=#{base_targets.length} targets=#{targets.length}"
308
+ "base=#{base_targets.length} wrappers=#{wrapper_targets.length} targets=#{targets.length}"
221
309
  )
222
310
  targets
223
311
  end
@@ -264,7 +352,7 @@ def constructor_usable_for_codegen?(ast, qt_class)
264
352
  ctor_decls = collect_constructor_decls(ast, qt_class)
265
353
  ctor_decls.any? do |decl|
266
354
  constructor_keysequence_parent_type(decl) ||
267
- constructor_supports_parent_only?(decl) ||
355
+ constructor_supports_parent_only?(decl) ||
268
356
  constructor_supports_no_args?(decl) ||
269
357
  constructor_supports_string_path?(decl)
270
358
  end
@@ -272,28 +360,40 @@ end
272
360
 
273
361
  def build_base_spec_for_qt_class(ast, qt_class)
274
362
  ctor_decls = collect_constructor_decls(ast, qt_class)
363
+ parent_ctor = if abstract_class?(ast, qt_class)
364
+ { parent: false, mode: :wrap_only }
365
+ else
366
+ constructor_spec_for_qt_class(ast, qt_class, ctor_decls)
367
+ end
368
+ base_spec_hash(qt_class, parent_ctor)
369
+ end
370
+
371
+ def constructor_spec_for_qt_class(ast, qt_class, ctor_decls)
275
372
  keysequence_parent_type = ctor_decls.filter_map { |decl| constructor_keysequence_parent_type(decl) }.first
276
373
  parent_type = ctor_decls.filter_map { |decl| parent_constructor_first_type(decl) }.first
277
- string_path_cast = ctor_decls.filter_map do |decl|
374
+ string_path_cast = constructor_string_path_cast(ctor_decls)
375
+ widget_child = qt_class != 'QWidget' && class_inherits?(ast, qt_class, 'QWidget')
376
+
377
+ if keysequence_parent_type
378
+ { parent: true, parent_type: keysequence_parent_type, mode: :keysequence_parent, register_in_parent: widget_child }
379
+ elsif parent_type
380
+ parent_constructor_for_type(parent_type, widget_child)
381
+ elsif string_path_cast
382
+ string_path_constructor(string_path_cast)
383
+ elsif ctor_decls.any? { |decl| constructor_supports_no_args?(decl) }
384
+ { parent: false }
385
+ else
386
+ { parent: false, mode: :wrap_only }
387
+ end
388
+ end
389
+
390
+ def constructor_string_path_cast(ctor_decls)
391
+ ctor_decls.filter_map do |decl|
278
392
  parsed = parse_method_signature(decl)
279
393
  next nil unless parsed && parsed[:params].first
280
394
 
281
395
  constructor_string_like_arg_cast(parsed[:params].first[:type])
282
396
  end.first
283
- widget_child = qt_class != 'QWidget' && class_inherits?(ast, qt_class, 'QWidget')
284
- parent_ctor =
285
- if keysequence_parent_type
286
- { parent: true, parent_type: keysequence_parent_type, mode: :keysequence_parent, register_in_parent: widget_child }
287
- elsif parent_type
288
- parent_constructor_for_type(parent_type, widget_child)
289
- elsif string_path_cast
290
- string_path_constructor(string_path_cast)
291
- elsif ctor_decls.any? { |decl| constructor_supports_no_args?(decl) }
292
- { parent: false }
293
- else
294
- { parent: false, mode: :wrap_only }
295
- end
296
- base_spec_hash(qt_class, parent_ctor)
297
397
  end
298
398
 
299
399
  def base_spec_hash(qt_class, parent_ctor)
@@ -920,13 +920,50 @@ end
920
920
  def append_ruby_widget_methods(lines, spec)
921
921
  spec[:methods].each do |method|
922
922
  call_args = ['@handle'] + method[:args].map { |arg| arg[:name] }
923
- native_call = "Native.#{spec[:prefix]}_#{to_snake(method[:qt_name])}(#{call_args.join(', ')})"
923
+ native_name = method_function_name(spec, method).delete_prefix('qt_ruby_')
924
+ native_call = "Native.#{native_name}(#{call_args.join(', ')})"
924
925
  append_ruby_native_call_method(lines, method: method, native_call: native_call, indent: ' ')
925
926
  append_ruby_property_writer(lines, method: method, indent: ' ')
926
927
  lines << ''
927
928
  end
928
929
  end
929
930
 
931
+ def assign_unique_native_method_names(specs)
932
+ used = Set.new
933
+ specs.map do |spec|
934
+ methods = spec[:methods].map do |method|
935
+ assign_unique_native_method_name(spec, method, used)
936
+ end
937
+ spec.merge(methods: methods)
938
+ end
939
+ end
940
+
941
+ def assign_unique_native_method_name(spec, method, used)
942
+ native_name = default_method_function_name(spec, method)
943
+ return used_add_and_return(used, native_name, method) unless used.include?(native_name)
944
+
945
+ unique_name = next_unique_native_method_name(spec, method, used)
946
+ debug_log("native name collision #{native_name} -> #{unique_name}")
947
+ used << unique_name
948
+ method.merge(native_name: unique_name)
949
+ end
950
+
951
+ def used_add_and_return(used, value, result)
952
+ used << value
953
+ result
954
+ end
955
+
956
+ def next_unique_native_method_name(spec, method, used)
957
+ base = "qt_ruby_#{spec[:prefix]}_qt_#{to_snake(method[:qt_name])}"
958
+ candidate = base
959
+ counter = 2
960
+ while used.include?(candidate)
961
+ candidate = "#{base}_#{counter}"
962
+ counter += 1
963
+ end
964
+ candidate
965
+ end
966
+
930
967
  def generate_ruby_widget_class(lines, spec, specs_by_qt, super_qt_by_qt, qt_to_ruby)
931
968
  metadata = ruby_api_metadata_for_spec(spec, specs_by_qt, super_qt_by_qt)
932
969
  super_ruby = ruby_super_class_for_spec(spec, super_qt_by_qt, qt_to_ruby)
@@ -1202,7 +1239,8 @@ free_function_specs = timed('build_free_function_specs') { qt_free_function_spec
1202
1239
  base_specs = timed('build_base_specs') { build_base_specs(ast) }
1203
1240
  timed('validate_qt_api') { validate_qt_api!(ast, base_specs) }
1204
1241
  expanded_specs = timed('expand_auto_methods') { expand_auto_methods(base_specs, ast) }
1205
- effective_specs = timed('enrich_specs_with_properties') { enrich_specs_with_properties(expanded_specs, ast) }
1242
+ property_specs = timed('enrich_specs_with_properties') { enrich_specs_with_properties(expanded_specs, ast) }
1243
+ effective_specs = timed('assign_unique_native_method_names') { assign_unique_native_method_names(property_specs) }
1206
1244
  super_qt_by_qt, wrapper_qt_classes = timed('build_generated_inheritance') do
1207
1245
  build_generated_inheritance(ast, effective_specs)
1208
1246
  end
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.8
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maksim Veynberg
@@ -100,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
100
  - !ruby/object:Gem::Version
101
101
  version: '0'
102
102
  requirements: []
103
- rubygems_version: 4.0.9
103
+ rubygems_version: 4.0.10
104
104
  specification_version: 4
105
105
  summary: Ruby bindings for Qt 6.4.2+
106
106
  test_files: []