qt 0.1.7 → 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: be7294fb17e8d0cd4eeb66cc86fe6ee91940a95d5a6f3afaf9dfa44fb54445ba
4
- data.tar.gz: 20683a100daf8ca9299a2227ae2605acbde2393e2444d020625c058cbc76993c
3
+ metadata.gz: 50d48e8571c76d7413f893a6189b7670629bd3a80123a131bf9cc00ffe6d818e
4
+ data.tar.gz: bcb0081e9ebabc04175f7f041fdf8442293d08191e5ee6e2b26164d32c730942
5
5
  SHA512:
6
- metadata.gz: 151a6423964153f8e5f54cba0cc7c54d1ff6b4a7fbd10b003d2a19f6263831493d18ed6fbaeabf88d5791d3e3326e356ba50e0d5332e212c75341bf6ea3c2f50
7
- data.tar.gz: 9e7542fc4bc481b7c3110f4b55aa4b33dd327babbe4aaad3dc4078ce2bd404a97a90022f1e49d97bd3ae05cb805e4db8fd0aef13e368974505dc361d29edfdd9
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
@@ -65,7 +68,6 @@ module Qt
65
68
  klass = Qt.const_get(normalized_qt_class, false)
66
69
  return nil unless klass.is_a?(Class)
67
70
  return nil unless klass.const_defined?(:QT_CLASS, false)
68
- return nil unless klass <= Qt::QObject
69
71
 
70
72
  klass
71
73
  rescue NameError
@@ -87,6 +89,7 @@ module Qt
87
89
 
88
90
  pointer = wrapper.handle
89
91
  return wrapper if null_pointer?(pointer)
92
+ return wrapper unless qobject_wrapper?(wrapper.class)
90
93
 
91
94
  cached = cached_wrapper_for(pointer)
92
95
  return cached if cached
@@ -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]
@@ -157,6 +188,10 @@ module Qt
157
188
  depth
158
189
  end
159
190
 
191
+ def qobject_wrapper?(klass)
192
+ klass.is_a?(Class) && klass <= Qt::QObject
193
+ end
194
+
160
195
  def normalize_expected_qt_class(expected_qt_class)
161
196
  return nil if expected_qt_class.nil?
162
197
 
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.7'
4
+ VERSION = '0.1.9'
5
5
  end
@@ -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'
@@ -95,7 +96,7 @@ def map_pointer_cpp_return_type(type, ast: nil)
95
96
 
96
97
  info = { ffi_return: :pointer }
97
98
  base_type = type.sub(/\s*\*\z/, '').strip
98
- if ast && class_inherits?(ast, base_type, 'QObject')
99
+ if ast && ast_class_index(ast)[:methods_by_class].key?(base_type)
99
100
  info[:pointer_class] = base_type
100
101
  end
101
102
 
@@ -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
@@ -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
@@ -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)
@@ -160,7 +244,7 @@ def related_value_class_candidate?(ast, qt_class, all_classes, template_classes)
160
244
  return false if abstract_class?(ast, qt_class)
161
245
  return false if class_inherits?(ast, qt_class, 'QObject')
162
246
 
163
- constructor_usable_for_codegen?(ast, qt_class)
247
+ true
164
248
  end
165
249
 
166
250
  def related_qt_type_names(ast, qt_class)
@@ -178,11 +262,12 @@ end
178
262
 
179
263
  def append_related_qt_types_from_decl(out, parsed, method_name)
180
264
  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|
265
+ candidates << [related_qt_type_name(parsed[:return_type]), parsed[:return_type].to_s] if parsed[:return_type]
266
+ parsed[:params].each { |param| candidates << [related_qt_type_name(param[:type]), param[:type].to_s] }
267
+ candidates.each do |candidate, raw_type|
184
268
  next if candidate.empty? || !candidate.start_with?('Q')
185
- next unless related_type_name_matches_method?(candidate, method_name)
269
+ next unless related_type_name_matches_method?(candidate, method_name) ||
270
+ (qt_pointer_type?(raw_type) && candidate.end_with?('Item'))
186
271
 
187
272
  out << candidate
188
273
  end
@@ -195,6 +280,14 @@ def related_type_name_matches_method?(qt_type, method_name)
195
280
  method_name.to_s.downcase.include?(token)
196
281
  end
197
282
 
283
+ def qt_pointer_type?(raw_type)
284
+ raw_type.to_s.strip.end_with?('*')
285
+ end
286
+
287
+ def related_qt_type_name(raw_type)
288
+ normalized_cpp_type_name(raw_type).to_s.delete_suffix('*')
289
+ end
290
+
198
291
  def discover_target_qt_classes(ast, scope)
199
292
  all_classes = q_class_names(ast)
200
293
  template_classes = template_qt_classes(ast)
@@ -206,9 +299,13 @@ def discover_target_qt_classes(ast, scope)
206
299
  targets = timed("discover_target_qt_classes/#{scope}/related") do
207
300
  discover_related_value_classes(ast, base_targets, all_classes, template_classes).sort
208
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
209
306
  debug_log(
210
307
  "discover_target_qt_classes scope=#{scope} total_q=#{all_classes.length} " \
211
- "base=#{base_targets.length} targets=#{targets.length}"
308
+ "base=#{base_targets.length} wrappers=#{wrapper_targets.length} targets=#{targets.length}"
212
309
  )
213
310
  targets
214
311
  end
@@ -220,9 +317,24 @@ def widget_target_qt_class?(ast, qt_class)
220
317
 
221
318
  class_inherits?(ast, qt_class, 'QWidget') ||
222
319
  class_inherits?(ast, qt_class, 'QLayout') ||
320
+ widget_item_target_qt_class?(ast, qt_class) ||
223
321
  qt_class == 'QTableWidgetItem'
224
322
  end
225
323
 
324
+ def widget_item_target_qt_class?(ast, qt_class)
325
+ return false unless qt_class.end_with?('Item')
326
+ return false if class_inherits?(ast, qt_class, 'QObject')
327
+
328
+ collect_constructor_decls(ast, qt_class).any? do |decl|
329
+ parsed = parse_method_signature(decl)
330
+ next false unless parsed
331
+
332
+ parsed[:params].any? do |param|
333
+ normalized_cpp_type_name(param[:type]).to_s.match?(/\AQ\w*Widget\*\z/)
334
+ end
335
+ end
336
+ end
337
+
226
338
  def qobject_target_qt_class?(ast, qt_class)
227
339
  return false if qt_class.end_with?('Private')
228
340
  return false if qt_class == 'QApplication'
@@ -240,7 +352,7 @@ def constructor_usable_for_codegen?(ast, qt_class)
240
352
  ctor_decls = collect_constructor_decls(ast, qt_class)
241
353
  ctor_decls.any? do |decl|
242
354
  constructor_keysequence_parent_type(decl) ||
243
- constructor_supports_parent_only?(decl) ||
355
+ constructor_supports_parent_only?(decl) ||
244
356
  constructor_supports_no_args?(decl) ||
245
357
  constructor_supports_string_path?(decl)
246
358
  end
@@ -248,26 +360,40 @@ end
248
360
 
249
361
  def build_base_spec_for_qt_class(ast, qt_class)
250
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)
251
372
  keysequence_parent_type = ctor_decls.filter_map { |decl| constructor_keysequence_parent_type(decl) }.first
252
373
  parent_type = ctor_decls.filter_map { |decl| parent_constructor_first_type(decl) }.first
253
- 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|
254
392
  parsed = parse_method_signature(decl)
255
393
  next nil unless parsed && parsed[:params].first
256
394
 
257
395
  constructor_string_like_arg_cast(parsed[:params].first[:type])
258
396
  end.first
259
- widget_child = qt_class != 'QWidget' && class_inherits?(ast, qt_class, 'QWidget')
260
- parent_ctor =
261
- if keysequence_parent_type
262
- { parent: true, parent_type: keysequence_parent_type, mode: :keysequence_parent, register_in_parent: widget_child }
263
- elsif parent_type
264
- parent_constructor_for_type(parent_type, widget_child)
265
- elsif string_path_cast
266
- string_path_constructor(string_path_cast)
267
- else
268
- { parent: false }
269
- end
270
- base_spec_hash(qt_class, parent_ctor)
271
397
  end
272
398
 
273
399
  def base_spec_hash(qt_class, parent_ctor)
@@ -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 << ''
@@ -717,6 +720,8 @@ def setter_alias_base_name(method)
717
720
  return nil unless method[:args].length == 1
718
721
  return nil if method[:property]
719
722
 
723
+ return 'cursor' if method[:qt_name] == 'setCursor'
724
+
720
725
  property_name_from_setter(method[:qt_name] || method[:ruby_name].to_s)
721
726
  end
722
727
 
@@ -747,7 +752,10 @@ def qapplication_singleton_forwarder_names(methods, singleton_setter_aliases)
747
752
  end
748
753
 
749
754
  def append_widget_initializer(lines, spec:, widget_root:, indent:)
750
- 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
751
759
  append_string_path_initializer(lines, spec, indent)
752
760
  elsif spec[:constructor][:mode] == :keysequence_parent
753
761
  append_keysequence_parent_initializer(lines, spec, widget_root, indent)
@@ -761,6 +769,12 @@ def append_widget_initializer(lines, spec:, widget_root:, indent:)
761
769
  lines << "#{indent}end"
762
770
  end
763
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
+
764
778
  def append_parent_widget_initializer(lines, spec, widget_root, indent)
765
779
  lines << "#{indent}def initialize(parent = nil)"
766
780
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(parent&.handle)"
@@ -906,13 +920,50 @@ end
906
920
  def append_ruby_widget_methods(lines, spec)
907
921
  spec[:methods].each do |method|
908
922
  call_args = ['@handle'] + method[:args].map { |arg| arg[:name] }
909
- 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(', ')})"
910
925
  append_ruby_native_call_method(lines, method: method, native_call: native_call, indent: ' ')
911
926
  append_ruby_property_writer(lines, method: method, indent: ' ')
912
927
  lines << ''
913
928
  end
914
929
  end
915
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
+
916
967
  def generate_ruby_widget_class(lines, spec, specs_by_qt, super_qt_by_qt, qt_to_ruby)
917
968
  metadata = ruby_api_metadata_for_spec(spec, specs_by_qt, super_qt_by_qt)
918
969
  super_ruby = ruby_super_class_for_spec(spec, super_qt_by_qt, qt_to_ruby)
@@ -1049,6 +1100,7 @@ def collect_enum_constants_for_scope(ast, target_scope, warnings = [])
1049
1100
  next unless node['kind'] == 'EnumDecl'
1050
1101
  next unless scope == target_scope
1051
1102
 
1103
+ next_value = 0
1052
1104
  Array(node['inner']).each do |entry|
1053
1105
  next unless entry['kind'] == 'EnumConstantDecl'
1054
1106
 
@@ -1058,9 +1110,14 @@ def collect_enum_constants_for_scope(ast, target_scope, warnings = [])
1058
1110
 
1059
1111
  raw_value = ast_extract_first_value(entry)
1060
1112
  value = parse_ast_integer_value(raw_value)
1061
- next if value.nil?
1113
+ if value.nil?
1114
+ value = next_value
1115
+ else
1116
+ next_value = value
1117
+ end
1062
1118
 
1063
1119
  append_constant_with_conflict_warning(constants, name, value, warnings, target_scope.join('::'))
1120
+ next_value = value + 1
1064
1121
  end
1065
1122
  end
1066
1123
 
@@ -1075,6 +1132,9 @@ def collect_qt_namespace_enum_constants(ast, warnings = [])
1075
1132
 
1076
1133
  append_constant_with_conflict_warning(constants, alias_name, value, warnings, 'Qt::QEventAlias')
1077
1134
  end
1135
+ collect_enum_constants_for_scope(ast, ['QCursor'], warnings).each do |name, value|
1136
+ append_constant_with_conflict_warning(constants, name, value, warnings, 'Qt::QCursorAlias')
1137
+ end
1078
1138
  constants
1079
1139
  end
1080
1140
 
@@ -1179,7 +1239,8 @@ free_function_specs = timed('build_free_function_specs') { qt_free_function_spec
1179
1239
  base_specs = timed('build_base_specs') { build_base_specs(ast) }
1180
1240
  timed('validate_qt_api') { validate_qt_api!(ast, base_specs) }
1181
1241
  expanded_specs = timed('expand_auto_methods') { expand_auto_methods(base_specs, ast) }
1182
- 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) }
1183
1244
  super_qt_by_qt, wrapper_qt_classes = timed('build_generated_inheritance') do
1184
1245
  build_generated_inheritance(ast, effective_specs)
1185
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.7
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: 3.6.7
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: []