qt 0.1.8 → 0.2.0

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: c0e7757f47bb17be90f64d3bcdd28ba17e5e8099a505062a7e7c3dd71aaedf37
4
+ data.tar.gz: cbf55606a5a16e12cb4ef47b73756a1f3ef3b65c1e500583928a44a4d28b97c4
5
5
  SHA512:
6
- metadata.gz: 37e94ba0242411ddd9f46d6858f397f53e2d2d62e38ba7ccba9a15fc517f86291b015d82dd359dfe0eb5ce12a011ebeb379d95a23c607c1be299883d5c24ed2a
7
- data.tar.gz: 5fc3cc069da9615f8efb690d2888f5ec7f5c02422885e070331b4e271381405a082993c705d100c9e30b6ceb5661471701769c81e5ff6ff072024fe34c7b9c15
6
+ metadata.gz: 1e5e470938bc069179be121de52a5e2986479a7fa8dec030d743de246fef73812729bc57ea67d4958c076e42e8d23ef703c0931e51544f206352e3ba45a3c463
7
+ data.tar.gz: bc3d7083a9617ad49b2b139d0948bec69257ad5991be08525de8228e87aa3457bdd9d4d2745b399bf20f0266143428565e3fb5d88e039255687eb90b38c7efe9
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,15 +51,16 @@ 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[
44
61
  runtime_events.cpp
45
62
  runtime_signals.cpp
63
+ runtime_value_types.cpp
46
64
  ].map { |name| File.expand_path("../../ext/qt_ruby_bridge/#{name}", __dir__) }
47
65
 
48
66
  unless File.exist?(generated_cpp)
@@ -52,8 +70,7 @@ abort "Runtime header not found: #{runtime_hpp}" unless File.exist?(runtime_hpp)
52
70
  missing_runtime = runtime_cpp_files.reject { |path| File.exist?(path) }
53
71
  abort "Runtime source not found: #{missing_runtime.join(', ')}" unless missing_runtime.empty?
54
72
 
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)
73
+ FileUtils.cp(generated_cpp, LOCAL_CPP) unless File.exist?(LOCAL_CPP) && File.identical?(generated_cpp, LOCAL_CPP)
57
74
  runtime_cpp_files.each do |runtime_cpp|
58
75
  local_runtime_cpp = File.expand_path(File.basename(runtime_cpp))
59
76
  unless File.exist?(local_runtime_cpp) && File.identical?(runtime_cpp, local_runtime_cpp)
@@ -1,6 +1,8 @@
1
1
  #pragma once
2
2
 
3
3
  #include <QApplication>
4
+ #include <QList>
5
+ #include <QVariant>
4
6
 
5
7
  namespace QtRubyRuntime {
6
8
  using EventCallback = int (*)(void*, int, const char*);
@@ -13,6 +15,10 @@ QApplication* qapplication_new(const char* argv0);
13
15
  // Returns false when teardown is rejected by runtime safety checks.
14
16
  bool qapplication_delete(void* app_handle);
15
17
 
18
+ // FFI-safe conversion for public Qt QList<int> API parameters and results.
19
+ QList<int> qint_list_from_variant(const QVariant& value);
20
+ QVariant qvariant_from_qint_list(const QList<int>& value);
21
+
16
22
  void set_event_callback(void* callback_ptr);
17
23
  void watch_qobject_event(void* object_handle, int event_type);
18
24
  void unwatch_qobject_event(void* object_handle, int event_type);
@@ -19,6 +19,7 @@ namespace QtRubyRuntime {
19
19
  constexpr int kEventCallbackIgnore = 0;
20
20
  constexpr int kEventCallbackContinue = 1;
21
21
  constexpr int kEventCallbackConsume = 2;
22
+ constexpr int kEventCallbackReject = 3;
22
23
 
23
24
  EventCallback& event_callback_ref() {
24
25
  static EventCallback callback = nullptr;
@@ -234,6 +235,10 @@ class EventFilter : public QObject {
234
235
  event->accept();
235
236
  return true;
236
237
  }
238
+ if (callback_result == kEventCallbackReject) {
239
+ event->ignore();
240
+ return true;
241
+ }
237
242
  return QObject::eventFilter(watched, event);
238
243
  }
239
244
  };
@@ -244,10 +249,10 @@ EventFilter* event_filter_instance() {
244
249
  }
245
250
 
246
251
  void ensure_event_filter_installed() {
247
- static bool installed = false;
248
- if (!installed && qApp) {
252
+ static QApplication* installed_on = nullptr;
253
+ if (qApp && installed_on != qApp) {
249
254
  qApp->installEventFilter(event_filter_instance());
250
- installed = true;
255
+ installed_on = qApp;
251
256
  }
252
257
  }
253
258
 
@@ -0,0 +1,22 @@
1
+ #include "qt_ruby_runtime.hpp"
2
+
3
+ namespace QtRubyRuntime {
4
+ QList<int> qint_list_from_variant(const QVariant& value) {
5
+ QList<int> result;
6
+ const QVariantList values = value.toList();
7
+ result.reserve(values.size());
8
+ for (const QVariant& entry : values) {
9
+ result.append(entry.toInt());
10
+ }
11
+ return result;
12
+ }
13
+
14
+ QVariant qvariant_from_qint_list(const QList<int>& value) {
15
+ QVariantList result;
16
+ result.reserve(value.size());
17
+ for (int entry : value) {
18
+ result.append(entry);
19
+ }
20
+ return result;
21
+ }
22
+ } // namespace QtRubyRuntime
@@ -7,6 +7,7 @@ module Qt
7
7
  EVENT_RESULT_IGNORE = 0
8
8
  EVENT_RESULT_CONTINUE = 1
9
9
  EVENT_RESULT_CONSUME = 2
10
+ EVENT_RESULT_REJECT = 3
10
11
 
11
12
  module_function
12
13
 
@@ -20,6 +21,7 @@ module Qt
20
21
  return EVENT_RESULT_CONTINUE unless handlers && !handlers.empty?
21
22
 
22
23
  results = handlers.map { |handler| handler.call(payload) }
24
+ return EVENT_RESULT_REJECT if results.include?(:reject)
23
25
  return EVENT_RESULT_CONSUME if results.any? { |result| result == true || result == :consume }
24
26
  return EVENT_RESULT_IGNORE if results.any? { |result| result == false || result == :ignore }
25
27
 
@@ -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]
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qt
4
+ # Owns copied Qt value objects returned through opaque FFI handles.
5
+ module ValueWrapper
6
+ module_function
7
+
8
+ def wrap(handle, qt_class, delete_method)
9
+ return nil if handle.nil? || handle.null?
10
+
11
+ wrapper = Qt.const_get(qt_class).allocate
12
+ wrapper.instance_variable_set(:@handle, handle)
13
+ ObjectSpace.define_finalizer(wrapper, finalizer(delete_method, handle))
14
+ wrapper
15
+ end
16
+
17
+ def finalizer(delete_method, handle)
18
+ proc do
19
+ Qt::Native.public_send(delete_method, handle) if Qt::Native.available?
20
+ rescue StandardError
21
+ nil
22
+ end
23
+ end
24
+ private_class_method :finalizer
25
+ end
26
+ 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.8'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/qt.rb CHANGED
@@ -24,6 +24,7 @@ require_relative 'qt/date_time_codec'
24
24
  require_relative 'qt/key_sequence_codec'
25
25
  require_relative 'qt/object_list_codec'
26
26
  require_relative 'qt/variant_codec'
27
+ require_relative 'qt/value_wrapper'
27
28
  require_relative 'qt/inspectable'
28
29
  require_relative 'qt/object_wrapper'
29
30
  require_relative 'qt/application_lifecycle'
@@ -38,7 +38,7 @@ def map_cpp_intlike_arg_type(type_name, qt_class, int_cast_types)
38
38
  map_qualified_intlike_arg_type(type_name, qt_class, int_cast_types)
39
39
  end
40
40
 
41
- def map_cpp_arg_type(type_name, qt_class: nil, int_cast_types: nil)
41
+ def map_cpp_arg_type(type_name, qt_class: nil, int_cast_types: nil, ast: nil)
42
42
  raw = type_name.to_s.strip
43
43
  return nil if raw.end_with?('&') && !raw.start_with?('const ')
44
44
 
@@ -46,7 +46,7 @@ def map_cpp_arg_type(type_name, qt_class: nil, int_cast_types: nil)
46
46
 
47
47
  type = raw
48
48
  type = type.sub(/\Aconst\s+/, '').sub(/\s*&\z/, '').strip
49
- return nil if unsupported_cpp_type?(type)
49
+ return { ffi: :string, cast: :qint_list_from_variant } if type == 'QList<int>'
50
50
  return { ffi: :string, cast: :qstring } if type == 'QString'
51
51
  return { ffi: :string, cast: :qdatetime_from_utf8 } if type == 'QDateTime'
52
52
  return { ffi: :string, cast: :qdate_from_utf8 } if type == 'QDate'
@@ -58,6 +58,12 @@ def map_cpp_arg_type(type_name, qt_class: nil, int_cast_types: nil)
58
58
  return { ffi: :string, cast: :qvariant_from_utf8 } if type == 'QVariant'
59
59
  return { ffi: :string } if compact_raw.match?(/\Aconst\s+char\s*\*\z/)
60
60
 
61
+ if ast_qt_value_class?(ast, type)
62
+ return { ffi: :pointer, cast: :qt_value_ref, value_class: type }
63
+ end
64
+
65
+ return nil if unsupported_cpp_type?(type)
66
+
61
67
  map_cpp_pointer_arg_type(type, qt_class) || map_cpp_intlike_arg_type(type, qt_class, int_cast_types)
62
68
  end
63
69
 
@@ -70,11 +76,25 @@ end
70
76
 
71
77
  def map_cpp_return_type(type_name, ast: nil)
72
78
  raw = type_name.to_s.strip
79
+ type = raw.sub(/\Aconst\s+/, '').sub(/\s*&\z/, '').strip
80
+ return { ffi_return: :string, return_cast: :qint_list_to_variant } if type == 'QList<int>'
73
81
  return nil if unsupported_cpp_type?(raw)
74
82
  return nil if raw.start_with?('const ') && raw.end_with?('*')
75
83
 
76
- type = raw.sub(/\Aconst\s+/, '').sub(/\s*&\z/, '').strip
77
- map_scalar_cpp_return_type(type) || map_pointer_cpp_return_type(type, ast: ast)
84
+ map_scalar_cpp_return_type(type) || map_pointer_cpp_return_type(type, ast: ast) || map_qt_value_return_type(type, ast: ast)
85
+ end
86
+
87
+ def ast_qt_value_class?(ast, type)
88
+ return false unless ast && type.match?(/\AQ[A-Z]\w*\z/)
89
+ return false unless ast_class_index(ast)[:methods_by_class].key?(type)
90
+
91
+ !class_inherits?(ast, type, 'QObject')
92
+ end
93
+
94
+ def map_qt_value_return_type(type, ast:)
95
+ return nil unless ast_qt_value_class?(ast, type)
96
+
97
+ { ffi_return: :pointer, return_cast: :qt_value_copy, value_class: type }
78
98
  end
79
99
 
80
100
  def map_scalar_cpp_return_type(type)
@@ -135,7 +155,7 @@ def parse_method_signature(method_decl)
135
155
  }
136
156
  end
137
157
 
138
- def build_auto_method_args(parsed, entry, qt_class, int_cast_types)
158
+ def build_auto_method_args(parsed, entry, qt_class, int_cast_types, ast)
139
159
  arg_cast_overrides = Array(entry[:arg_casts])
140
160
  params = parsed[:params]
141
161
  required_arg_count = 0
@@ -143,7 +163,7 @@ def build_auto_method_args(parsed, entry, qt_class, int_cast_types)
143
163
 
144
164
  params.each_with_index do |param, idx|
145
165
  cast_override = arg_cast_overrides[idx]
146
- arg_info = map_cpp_arg_type(param[:type], qt_class: qt_class, int_cast_types: int_cast_types)
166
+ arg_info = map_cpp_arg_type(param[:type], qt_class: qt_class, int_cast_types: int_cast_types, ast: ast)
147
167
  arg_info ||= { ffi: :int } if cast_override
148
168
  unless arg_info
149
169
  return nil unless skip_unsupported_optional_tail?(params, idx, param)
@@ -151,7 +171,12 @@ def build_auto_method_args(parsed, entry, qt_class, int_cast_types)
151
171
  break
152
172
  end
153
173
 
154
- args << { name: param[:name], ffi: arg_info[:ffi], cast: cast_override || arg_info[:cast] }.compact
174
+ args << {
175
+ name: param[:name],
176
+ ffi: arg_info[:ffi],
177
+ cast: cast_override || arg_info[:cast],
178
+ value_class: arg_info[:value_class]
179
+ }.compact
155
180
  required_arg_count += 1 unless param[:has_default]
156
181
  end
157
182
 
@@ -174,6 +199,7 @@ def build_auto_method_hash(entry, ret_info, args, required_arg_count)
174
199
  }
175
200
  method[:return_cast] = ret_info[:return_cast] if ret_info[:return_cast]
176
201
  method[:pointer_class] = ret_info[:pointer_class] if ret_info[:pointer_class]
202
+ method[:value_class] = ret_info[:value_class] if ret_info[:value_class]
177
203
  method
178
204
  end
179
205
 
@@ -184,7 +210,7 @@ def build_auto_method_from_decl(method_decl, entry, qt_class:, int_cast_types:,
184
210
  ret_info = map_cpp_return_type(parsed[:return_type], ast: ast)
185
211
  return nil unless ret_info
186
212
 
187
- args, required_arg_count = build_auto_method_args(parsed, entry, qt_class, int_cast_types)
213
+ args, required_arg_count = build_auto_method_args(parsed, entry, qt_class, int_cast_types, ast)
188
214
  return nil unless args
189
215
 
190
216
  build_auto_method_hash(entry, ret_info, args, required_arg_count)
@@ -352,7 +378,11 @@ def resolve_auto_method(ast, qt_class, auto_entry)
352
378
  built = resolve_auto_method_built_candidates(ast, qt_class, entry)
353
379
  return per_ast_cache[cache_key] = nil unless built
354
380
 
355
- per_ast_cache[cache_key] = built.min_by { |candidate| candidate[:method][:args].length }[:method]
381
+ per_ast_cache[cache_key] = built.min_by do |candidate|
382
+ method = candidate[:method]
383
+ value_arg_count = method[:args].count { |arg| arg[:cast] == :qt_value_ref }
384
+ [value_arg_count, method[:args].length]
385
+ end[:method]
356
386
  end
357
387
 
358
388
  def auto_entries_for_spec(spec, ast)
@@ -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
@@ -10,6 +10,8 @@ class CppMethodReturnEmitter
10
10
 
11
11
  def emit
12
12
  return emit_void if method[:ffi_return] == :void
13
+ return emit_qt_value_copy if method[:return_cast] == :qt_value_copy
14
+ return emit_qint_list if method[:return_cast] == :qint_list_to_variant
13
15
  return emit_qstring if qstring_return?
14
16
  return emit_qobject_list if qobject_list_return?
15
17
  return emit_qdatetime if qdatetime_return?
@@ -54,52 +56,86 @@ class CppMethodReturnEmitter
54
56
  end
55
57
 
56
58
  def emit_qstring
57
- lines << " const QString value = #{invocation};"
58
- lines << ' thread_local QByteArray utf8;'
59
- lines << ' utf8 = value.toUtf8();'
60
- lines << ' return utf8.constData();'
59
+ result = unique_local_name('result')
60
+ utf8 = unique_local_name('utf8')
61
+ lines << " const QString #{result} = #{invocation};"
62
+ lines << " thread_local QByteArray #{utf8};"
63
+ lines << " #{utf8} = #{result}.toUtf8();"
64
+ lines << " return #{utf8}.constData();"
61
65
  end
62
66
 
63
67
  def emit_qvariant
64
- lines << " const QVariant value = #{invocation};"
65
- lines << ' thread_local QByteArray utf8;'
66
- lines << ' utf8 = qvariant_to_bridge_string(value).toUtf8();'
67
- lines << ' return utf8.constData();'
68
+ result = unique_local_name('result')
69
+ utf8 = unique_local_name('utf8')
70
+ lines << " const QVariant #{result} = #{invocation};"
71
+ lines << " thread_local QByteArray #{utf8};"
72
+ lines << " #{utf8} = qvariant_to_bridge_string(#{result}).toUtf8();"
73
+ lines << " return #{utf8}.constData();"
68
74
  end
69
75
 
70
76
  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();'
77
+ result = unique_local_name('result')
78
+ utf8 = unique_local_name('utf8')
79
+ lines << " const QObjectList #{result} = #{invocation};"
80
+ lines << " thread_local QByteArray #{utf8};"
81
+ lines << " #{utf8} = qobject_list_to_bridge_string(#{result}).toUtf8();"
82
+ lines << " return #{utf8}.constData();"
75
83
  end
76
84
 
77
85
  def emit_qdatetime
78
- lines << " const QDateTime value = #{invocation};"
79
- lines << ' thread_local QByteArray utf8;'
80
- lines << ' utf8 = qdatetime_to_bridge_string(value).toUtf8();'
81
- lines << ' return utf8.constData();'
86
+ result = unique_local_name('result')
87
+ utf8 = unique_local_name('utf8')
88
+ lines << " const QDateTime #{result} = #{invocation};"
89
+ lines << " thread_local QByteArray #{utf8};"
90
+ lines << " #{utf8} = qdatetime_to_bridge_string(#{result}).toUtf8();"
91
+ lines << " return #{utf8}.constData();"
82
92
  end
83
93
 
84
94
  def emit_qdate
85
- lines << " const QDate value = #{invocation};"
86
- lines << ' thread_local QByteArray utf8;'
87
- lines << ' utf8 = qdate_to_bridge_string(value).toUtf8();'
88
- lines << ' return utf8.constData();'
95
+ result = unique_local_name('result')
96
+ utf8 = unique_local_name('utf8')
97
+ lines << " const QDate #{result} = #{invocation};"
98
+ lines << " thread_local QByteArray #{utf8};"
99
+ lines << " #{utf8} = qdate_to_bridge_string(#{result}).toUtf8();"
100
+ lines << " return #{utf8}.constData();"
89
101
  end
90
102
 
91
103
  def emit_qtime
92
- lines << " const QTime value = #{invocation};"
93
- lines << ' thread_local QByteArray utf8;'
94
- lines << ' utf8 = qtime_to_bridge_string(value).toUtf8();'
95
- lines << ' return utf8.constData();'
104
+ result = unique_local_name('result')
105
+ utf8 = unique_local_name('utf8')
106
+ lines << " const QTime #{result} = #{invocation};"
107
+ lines << " thread_local QByteArray #{utf8};"
108
+ lines << " #{utf8} = qtime_to_bridge_string(#{result}).toUtf8();"
109
+ lines << " return #{utf8}.constData();"
96
110
  end
97
111
 
98
112
  def emit_pointer
99
113
  lines << " return const_cast<void*>(static_cast<const void*>(#{invocation}));"
100
114
  end
101
115
 
116
+ def emit_qt_value_copy
117
+ lines << " return new #{method[:value_class]}(#{invocation});"
118
+ end
119
+
120
+ def emit_qint_list
121
+ result = unique_local_name('result')
122
+ variant = unique_local_name('variant')
123
+ utf8 = unique_local_name('utf8')
124
+ lines << " const QList<int> #{result} = #{invocation};"
125
+ lines << " const QVariant #{variant} = QtRubyRuntime::qvariant_from_qint_list(#{result});"
126
+ lines << " thread_local QByteArray #{utf8};"
127
+ lines << " #{utf8} = qvariant_to_bridge_string(#{variant}).toUtf8();"
128
+ lines << " return #{utf8}.constData();"
129
+ end
130
+
102
131
  def emit_value
103
132
  lines << " return #{invocation};"
104
133
  end
134
+
135
+ def unique_local_name(suffix)
136
+ used = Array(method[:args]).map { |arg| arg[:name].to_s }.to_set
137
+ candidate = "qt_ruby_#{suffix}"
138
+ candidate += '_' while used.include?(candidate)
139
+ candidate
140
+ end
105
141
  end
@@ -15,9 +15,25 @@ def all_ffi_functions(specs, free_function_specs:)
15
15
  append_method_ffi_functions(fns, spec)
16
16
  end
17
17
 
18
+ value_wrapper_classes(specs, free_function_specs: free_function_specs).each do |qt_class|
19
+ fns << { name: qt_value_delete_function_name(qt_class), ffi_return: :void, args: [:pointer] }
20
+ end
21
+
18
22
  fns
19
23
  end
20
24
 
25
+ def value_wrapper_classes(specs, free_function_specs: [])
26
+ spec_classes = specs.flat_map do |spec|
27
+ spec[:methods].filter_map { |method| method[:value_class] if method[:return_cast] == :qt_value_copy }
28
+ end
29
+ free_classes = free_function_specs.filter_map { |spec| spec[:value_class] }
30
+ (spec_classes + free_classes).uniq.sort
31
+ end
32
+
33
+ def qt_value_delete_function_name(qt_class)
34
+ "qt_ruby_#{to_snake(qt_class)}_value_delete"
35
+ end
36
+
21
37
  def append_constructor_ffi_function(fns, spec)
22
38
  return if spec[:constructor][:mode] == :wrap_only
23
39
 
@@ -215,6 +215,8 @@ def build_qapplication_static_free_function_spec(ast, qt_name, int_cast_types)
215
215
  cpp_body =
216
216
  if candidate[:ffi_return] == :void
217
217
  ["QApplication::#{qt_name}();"]
218
+ elsif candidate[:return_cast] == :qt_value_copy
219
+ ["return new #{candidate[:value_class]}(QApplication::#{qt_name}());"]
218
220
  elsif candidate[:enum_cast]
219
221
  ["return static_cast<int>(QApplication::#{qt_name}());"]
220
222
  elsif candidate[:ffi_return] == :string
@@ -231,6 +233,7 @@ def build_qapplication_static_free_function_spec(ast, qt_name, int_cast_types)
231
233
  {
232
234
  name: "qt_ruby_#{native_name}",
233
235
  ffi_return: candidate[:ffi_return],
236
+ value_class: candidate[:value_class],
234
237
  args: [],
235
238
  cpp_return: ffi_return_to_cpp(candidate[:ffi_return]),
236
239
  cpp_args: '',
@@ -240,7 +243,8 @@ def build_qapplication_static_free_function_spec(ast, qt_name, int_cast_types)
240
243
  native: native_name,
241
244
  args: [],
242
245
  return_cast: candidate[:return_cast],
243
- pointer_class: candidate[:pointer_class]
246
+ pointer_class: candidate[:pointer_class],
247
+ value_class: candidate[:value_class]
244
248
  }
245
249
  }
246
250
  end
@@ -271,6 +275,7 @@ def qapplication_static_return_info(return_type, int_cast_types, ast:)
271
275
  ffi_return: mapped[:ffi_return],
272
276
  return_cast: mapped[:return_cast],
273
277
  pointer_class: mapped[:pointer_class],
278
+ value_class: mapped[:value_class],
274
279
  enum_cast: false
275
280
  }
276
281
  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,14 @@ 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
+ seed_classes = (targets + ['QApplication']).uniq
304
+ discover_related_qobject_wrapper_classes(ast, seed_classes, all_classes, template_classes, scope).sort
305
+ end
306
+ targets = (targets + wrapper_targets).uniq.sort
218
307
  debug_log(
219
308
  "discover_target_qt_classes scope=#{scope} total_q=#{all_classes.length} " \
220
- "base=#{base_targets.length} targets=#{targets.length}"
309
+ "base=#{base_targets.length} wrappers=#{wrapper_targets.length} targets=#{targets.length}"
221
310
  )
222
311
  targets
223
312
  end
@@ -264,7 +353,7 @@ def constructor_usable_for_codegen?(ast, qt_class)
264
353
  ctor_decls = collect_constructor_decls(ast, qt_class)
265
354
  ctor_decls.any? do |decl|
266
355
  constructor_keysequence_parent_type(decl) ||
267
- constructor_supports_parent_only?(decl) ||
356
+ constructor_supports_parent_only?(decl) ||
268
357
  constructor_supports_no_args?(decl) ||
269
358
  constructor_supports_string_path?(decl)
270
359
  end
@@ -272,28 +361,40 @@ end
272
361
 
273
362
  def build_base_spec_for_qt_class(ast, qt_class)
274
363
  ctor_decls = collect_constructor_decls(ast, qt_class)
364
+ parent_ctor = if abstract_class?(ast, qt_class)
365
+ { parent: false, mode: :wrap_only }
366
+ else
367
+ constructor_spec_for_qt_class(ast, qt_class, ctor_decls)
368
+ end
369
+ base_spec_hash(qt_class, parent_ctor)
370
+ end
371
+
372
+ def constructor_spec_for_qt_class(ast, qt_class, ctor_decls)
275
373
  keysequence_parent_type = ctor_decls.filter_map { |decl| constructor_keysequence_parent_type(decl) }.first
276
374
  parent_type = ctor_decls.filter_map { |decl| parent_constructor_first_type(decl) }.first
277
- string_path_cast = ctor_decls.filter_map do |decl|
375
+ string_path_cast = constructor_string_path_cast(ctor_decls)
376
+ widget_child = qt_class != 'QWidget' && class_inherits?(ast, qt_class, 'QWidget')
377
+
378
+ if keysequence_parent_type
379
+ { parent: true, parent_type: keysequence_parent_type, mode: :keysequence_parent, register_in_parent: widget_child }
380
+ elsif parent_type
381
+ parent_constructor_for_type(parent_type, widget_child)
382
+ elsif string_path_cast
383
+ string_path_constructor(string_path_cast)
384
+ elsif ctor_decls.any? { |decl| constructor_supports_no_args?(decl) }
385
+ { parent: false }
386
+ else
387
+ { parent: false, mode: :wrap_only }
388
+ end
389
+ end
390
+
391
+ def constructor_string_path_cast(ctor_decls)
392
+ ctor_decls.filter_map do |decl|
278
393
  parsed = parse_method_signature(decl)
279
394
  next nil unless parsed && parsed[:params].first
280
395
 
281
396
  constructor_string_like_arg_cast(parsed[:params].first[:type])
282
397
  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
398
  end
298
399
 
299
400
  def base_spec_hash(qt_class, parent_ctor)
@@ -250,7 +250,9 @@ def arg_expr(arg)
250
250
  when :qicon_ref then "*static_cast<QIcon*>(#{arg[:name]})"
251
251
  when :qany_string_view then "QAnyStringView(as_qstring(#{arg[:name]}))"
252
252
  when :qvariant_from_utf8 then "qvariant_from_bridge_value(#{arg[:name]})"
253
+ when :qint_list_from_variant then "QtRubyRuntime::qint_list_from_variant(qvariant_from_bridge_value(#{arg[:name]}))"
253
254
  when :alignment then "static_cast<Qt::Alignment>(#{arg[:name]})"
255
+ when :qt_value_ref then "*static_cast<#{arg[:value_class]}*>(#{arg[:name]})"
254
256
  when String then "static_cast<#{arg[:cast]}>(#{arg[:name]})"
255
257
  else
256
258
  arg[:name]
@@ -335,6 +337,15 @@ def generate_cpp_delete(lines)
335
337
  lines << '}'
336
338
  end
337
339
 
340
+ def generate_cpp_qt_value_deletes(lines, specs, free_function_specs)
341
+ value_wrapper_classes(specs, free_function_specs: free_function_specs).each do |qt_class|
342
+ lines << "extern \"C\" void #{qt_value_delete_function_name(qt_class)}(void* handle) {"
343
+ lines << " delete static_cast<#{qt_class}*>(handle);"
344
+ lines << '}'
345
+ lines << ''
346
+ end
347
+ end
348
+
338
349
  def cpp_method_signature(method)
339
350
  ['void* handle'] + method[:args].map { |arg| "#{ffi_to_cpp_type(arg[:ffi])} #{arg[:name]}" }
340
351
  end
@@ -381,6 +392,7 @@ def generate_cpp_bridge(specs, free_function_specs)
381
392
 
382
393
  specs.each { |spec| append_cpp_spec_methods(lines, spec) }
383
394
 
395
+ generate_cpp_qt_value_deletes(lines, specs, free_function_specs)
384
396
  generate_cpp_delete(lines)
385
397
  "#{lines.join("\n")}\n"
386
398
  end
@@ -640,6 +652,7 @@ def optional_arg_replacement(arg, safe)
640
652
  when :bool then "(#{safe}.nil? ? false : #{safe})"
641
653
  when :pointer then safe
642
654
  when :string
655
+ return "(#{safe}.nil? ? Qt::VariantCodec.encode([]) : Qt::VariantCodec.encode(#{safe}))" if arg[:cast] == :qint_list_from_variant
643
656
  return "(#{safe}.nil? ? '' : Qt::VariantCodec.encode(#{safe}))" if arg[:cast] == :qvariant_from_utf8
644
657
  return "(#{safe}.nil? ? '' : Qt::DateTimeCodec.encode_qdatetime(#{safe}))" if arg[:cast] == :qdatetime_from_utf8
645
658
  return "(#{safe}.nil? ? '' : Qt::DateTimeCodec.encode_qdate(#{safe}))" if arg[:cast] == :qdate_from_utf8
@@ -653,8 +666,13 @@ def optional_arg_replacement(arg, safe)
653
666
  end
654
667
 
655
668
  def ruby_arg_call_value(arg, safe, optional:)
669
+ if arg[:cast] == :qt_value_ref
670
+ return optional ? "(#{safe}.nil? ? nil : #{safe}.handle)" : "#{safe}.handle"
671
+ end
672
+
656
673
  return "Qt::StringCodec.to_qt_text(#{safe})" if text_bridge_arg?(arg) && !optional
657
674
  return "Qt::VariantCodec.encode(#{safe})" if arg[:cast] == :qvariant_from_utf8 && !optional
675
+ return "Qt::VariantCodec.encode(#{safe})" if arg[:cast] == :qint_list_from_variant && !optional
658
676
  return "Qt::DateTimeCodec.encode_qdatetime(#{safe})" if arg[:cast] == :qdatetime_from_utf8 && !optional
659
677
  return "Qt::DateTimeCodec.encode_qdate(#{safe})" if arg[:cast] == :qdate_from_utf8 && !optional
660
678
  return "Qt::DateTimeCodec.encode_qtime(#{safe})" if arg[:cast] == :qtime_from_utf8 && !optional
@@ -698,9 +716,14 @@ def ruby_native_method_body(method, rewritten_native_call)
698
716
  return "Qt::ObjectListCodec.decode(#{rewritten_native_call}, '#{method[:object_list_class]}')"
699
717
  end
700
718
  return "Qt::VariantCodec.decode(#{rewritten_native_call})" if method[:return_cast] == :qvariant_to_utf8
719
+ return "Qt::VariantCodec.decode(#{rewritten_native_call})" if method[:return_cast] == :qint_list_to_variant
701
720
  return "Qt::DateTimeCodec.decode_qdatetime(#{rewritten_native_call})" if method[:return_cast] == :qdatetime_to_utf8
702
721
  return "Qt::DateTimeCodec.decode_qdate(#{rewritten_native_call})" if method[:return_cast] == :qdate_to_utf8
703
722
  return "Qt::DateTimeCodec.decode_qtime(#{rewritten_native_call})" if method[:return_cast] == :qtime_to_utf8
723
+ if method[:return_cast] == :qt_value_copy
724
+ delete_method = qt_value_delete_function_name(method[:value_class])
725
+ return "Qt::ValueWrapper.wrap(#{rewritten_native_call}, '#{method[:value_class]}', :#{delete_method})"
726
+ end
704
727
  return "Qt::ObjectWrapper.wrap(#{rewritten_native_call}, '#{method[:pointer_class]}')" if method[:pointer_class]
705
728
 
706
729
  rewritten_native_call
@@ -899,6 +922,11 @@ def qapplication_class_method_body(method, native_call)
899
922
  return " Qt::DateTimeCodec.decode_qdatetime(#{native_call})" if method[:return_cast] == :qdatetime_to_utf8
900
923
  return " Qt::DateTimeCodec.decode_qdate(#{native_call})" if method[:return_cast] == :qdate_to_utf8
901
924
  return " Qt::DateTimeCodec.decode_qtime(#{native_call})" if method[:return_cast] == :qtime_to_utf8
925
+ return " Qt::VariantCodec.decode(#{native_call})" if method[:return_cast] == :qint_list_to_variant
926
+ if method[:return_cast] == :qt_value_copy
927
+ delete_method = qt_value_delete_function_name(method[:value_class])
928
+ return " Qt::ValueWrapper.wrap(#{native_call}, '#{method[:value_class]}', :#{delete_method})"
929
+ end
902
930
  return " Qt::ObjectWrapper.wrap(#{native_call}, '#{method[:pointer_class]}')" if method[:pointer_class]
903
931
 
904
932
  " #{native_call}"
@@ -920,13 +948,50 @@ end
920
948
  def append_ruby_widget_methods(lines, spec)
921
949
  spec[:methods].each do |method|
922
950
  call_args = ['@handle'] + method[:args].map { |arg| arg[:name] }
923
- native_call = "Native.#{spec[:prefix]}_#{to_snake(method[:qt_name])}(#{call_args.join(', ')})"
951
+ native_name = method_function_name(spec, method).delete_prefix('qt_ruby_')
952
+ native_call = "Native.#{native_name}(#{call_args.join(', ')})"
924
953
  append_ruby_native_call_method(lines, method: method, native_call: native_call, indent: ' ')
925
954
  append_ruby_property_writer(lines, method: method, indent: ' ')
926
955
  lines << ''
927
956
  end
928
957
  end
929
958
 
959
+ def assign_unique_native_method_names(specs)
960
+ used = Set.new
961
+ specs.map do |spec|
962
+ methods = spec[:methods].map do |method|
963
+ assign_unique_native_method_name(spec, method, used)
964
+ end
965
+ spec.merge(methods: methods)
966
+ end
967
+ end
968
+
969
+ def assign_unique_native_method_name(spec, method, used)
970
+ native_name = default_method_function_name(spec, method)
971
+ return used_add_and_return(used, native_name, method) unless used.include?(native_name)
972
+
973
+ unique_name = next_unique_native_method_name(spec, method, used)
974
+ debug_log("native name collision #{native_name} -> #{unique_name}")
975
+ used << unique_name
976
+ method.merge(native_name: unique_name)
977
+ end
978
+
979
+ def used_add_and_return(used, value, result)
980
+ used << value
981
+ result
982
+ end
983
+
984
+ def next_unique_native_method_name(spec, method, used)
985
+ base = "qt_ruby_#{spec[:prefix]}_qt_#{to_snake(method[:qt_name])}"
986
+ candidate = base
987
+ counter = 2
988
+ while used.include?(candidate)
989
+ candidate = "#{base}_#{counter}"
990
+ counter += 1
991
+ end
992
+ candidate
993
+ end
994
+
930
995
  def generate_ruby_widget_class(lines, spec, specs_by_qt, super_qt_by_qt, qt_to_ruby)
931
996
  metadata = ruby_api_metadata_for_spec(spec, specs_by_qt, super_qt_by_qt)
932
997
  super_ruby = ruby_super_class_for_spec(spec, super_qt_by_qt, qt_to_ruby)
@@ -1136,6 +1201,7 @@ def collect_qt_scoped_enum_constants(ast, warnings = [])
1136
1201
  next unless owner.match?(/\AQ[A-Z]\w*\z/)
1137
1202
  next if owner == 'Qt' || owner == 'QEvent'
1138
1203
 
1204
+ next_value = 0
1139
1205
  Array(node['inner']).each do |entry|
1140
1206
  next unless entry['kind'] == 'EnumConstantDecl'
1141
1207
 
@@ -1144,7 +1210,13 @@ def collect_qt_scoped_enum_constants(ast, warnings = [])
1144
1210
 
1145
1211
  raw_value = ast_extract_first_value(entry)
1146
1212
  value = parse_ast_integer_value(raw_value)
1147
- next if value.nil?
1213
+ value = next_value if value.nil? && !next_value.nil?
1214
+ if value.nil?
1215
+ next_value = nil
1216
+ next
1217
+ end
1218
+
1219
+ next_value = value + 1
1148
1220
 
1149
1221
  append_constant_with_conflict_warning(
1150
1222
  constants_by_owner[owner],
@@ -1202,7 +1274,8 @@ free_function_specs = timed('build_free_function_specs') { qt_free_function_spec
1202
1274
  base_specs = timed('build_base_specs') { build_base_specs(ast) }
1203
1275
  timed('validate_qt_api') { validate_qt_api!(ast, base_specs) }
1204
1276
  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) }
1277
+ property_specs = timed('enrich_specs_with_properties') { enrich_specs_with_properties(expanded_specs, ast) }
1278
+ effective_specs = timed('assign_unique_native_method_names') { assign_unique_native_method_names(property_specs) }
1206
1279
  super_qt_by_qt, wrapper_qt_classes = timed('build_generated_inheritance') do
1207
1280
  build_generated_inheritance(ast, effective_specs)
1208
1281
  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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maksim Veynberg
@@ -47,6 +47,7 @@ files:
47
47
  - ext/qt_ruby_bridge/qt_ruby_runtime.hpp
48
48
  - ext/qt_ruby_bridge/runtime_events.cpp
49
49
  - ext/qt_ruby_bridge/runtime_signals.cpp
50
+ - ext/qt_ruby_bridge/runtime_value_types.cpp
50
51
  - lib/qt.rb
51
52
  - lib/qt/application_lifecycle.rb
52
53
  - lib/qt/bridge.rb
@@ -66,6 +67,7 @@ files:
66
67
  - lib/qt/object_wrapper.rb
67
68
  - lib/qt/shortcut_compat.rb
68
69
  - lib/qt/string_codec.rb
70
+ - lib/qt/value_wrapper.rb
69
71
  - lib/qt/variant_codec.rb
70
72
  - lib/qt/version.rb
71
73
  - scripts/generate_bridge.rb
@@ -100,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
102
  - !ruby/object:Gem::Version
101
103
  version: '0'
102
104
  requirements: []
103
- rubygems_version: 4.0.9
105
+ rubygems_version: 4.0.10
104
106
  specification_version: 4
105
107
  summary: Ruby bindings for Qt 6.4.2+
106
108
  test_files: []