qt 0.1.9 → 0.2.1

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.
@@ -35,6 +35,7 @@ def build_qapplication_spec(ast)
35
35
  constructor: { parent: false, mode: :qapplication },
36
36
  class_methods: class_methods,
37
37
  methods: instance_methods,
38
+ auto_methods: :all,
38
39
  validate: { constructors: ['QApplication'], methods: ['exec'] }
39
40
  }
40
41
  end
@@ -66,8 +67,6 @@ def next_trace_base(fetch_bases, cur, visited)
66
67
  end
67
68
 
68
69
  def trace_generated_super_chain(fetch_bases, known_qt, qt_class, super_qt_by_qt)
69
- return if qt_class == 'QApplication'
70
-
71
70
  visited = {}
72
71
  prev = cur = qt_class
73
72
  loop do
@@ -154,13 +153,13 @@ def find_getter_decl(ast, qt_class, property)
154
153
  parsed = parse_method_signature(decl)
155
154
  next false unless parsed && parsed[:params].empty?
156
155
 
157
- map_cpp_return_type(parsed[:return_type], ast: ast)
156
+ map_cpp_return_type(parsed[:return_type], ast: ast, qt_class: qt_class)
158
157
  end
159
158
  end
160
159
 
161
160
  def build_property_getter_method(ast, getter_decl, property)
162
161
  parsed_getter = parse_method_signature(getter_decl)
163
- ret_info = map_cpp_return_type(parsed_getter[:return_type], ast: ast)
162
+ ret_info = map_cpp_return_type(parsed_getter[:return_type], ast: ast, qt_class: getter_decl['__owner_class'])
164
163
  return nil unless ret_info
165
164
 
166
165
  getter = {
@@ -250,7 +249,9 @@ def arg_expr(arg)
250
249
  when :qicon_ref then "*static_cast<QIcon*>(#{arg[:name]})"
251
250
  when :qany_string_view then "QAnyStringView(as_qstring(#{arg[:name]}))"
252
251
  when :qvariant_from_utf8 then "qvariant_from_bridge_value(#{arg[:name]})"
252
+ when :qint_list_from_variant then "QtRubyRuntime::qint_list_from_variant(qvariant_from_bridge_value(#{arg[:name]}))"
253
253
  when :alignment then "static_cast<Qt::Alignment>(#{arg[:name]})"
254
+ when :qt_value_ref then "*static_cast<#{arg[:value_class]}*>(#{arg[:name]})"
254
255
  when String then "static_cast<#{arg[:cast]}>(#{arg[:name]})"
255
256
  else
256
257
  arg[:name]
@@ -306,26 +307,27 @@ end
306
307
 
307
308
  def generate_cpp_constructor(lines, spec)
308
309
  name = ctor_function_name(spec)
310
+ qt_class = spec[:virtual_adapter] ? generated_virtual_subclass_name(spec) : spec[:qt_class]
309
311
 
310
312
  if spec[:constructor][:mode] == :qapplication
311
313
  emit_cpp_qapplication_constructor(lines, name)
312
314
  return
313
315
  end
314
316
  if spec[:constructor][:mode] == :string_path
315
- emit_cpp_string_path_constructor(lines, name, spec[:qt_class], spec[:constructor][:arg_cast])
317
+ emit_cpp_string_path_constructor(lines, name, qt_class, spec[:constructor][:arg_cast])
316
318
  return
317
319
  end
318
320
  if spec[:constructor][:mode] == :keysequence_parent
319
- emit_cpp_keysequence_parent_constructor(lines, name, spec)
321
+ emit_cpp_keysequence_parent_constructor(lines, name, spec.merge(qt_class: qt_class))
320
322
  return
321
323
  end
322
324
 
323
325
  unless spec[:constructor][:parent]
324
- emit_cpp_default_constructor(lines, name, spec[:qt_class])
326
+ emit_cpp_default_constructor(lines, name, qt_class)
325
327
  return
326
328
  end
327
329
 
328
- emit_cpp_parent_constructor(lines, name, spec)
330
+ emit_cpp_parent_constructor(lines, name, spec.merge(qt_class: qt_class))
329
331
  end
330
332
 
331
333
  def generate_cpp_delete(lines)
@@ -335,8 +337,18 @@ 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
- ['void* handle'] + method[:args].map { |arg| "#{ffi_to_cpp_type(arg[:ffi])} #{arg[:name]}" }
350
+ ['void* handle'] + (method[:dispatch] ? ['int argc'] : []) +
351
+ method[:args].map { |arg| "#{ffi_to_cpp_type(arg[:ffi])} #{arg[:name]}" }
340
352
  end
341
353
 
342
354
  def cpp_null_handle_return(method)
@@ -358,19 +370,37 @@ end
358
370
 
359
371
  def cpp_method_invocation(method)
360
372
  call_args = method[:args].map { |arg| arg_expr(arg) }.join(', ')
361
- "self_obj->#{method[:qt_name]}(#{call_args})"
373
+ target = method[:static] ? "#{method[:owner_class]}::#{method[:qt_name]}" : "self_obj->#{method[:qt_name]}"
374
+ "#{target}(#{call_args})"
362
375
  end
363
376
 
364
377
  def generate_cpp_method(lines, spec, method)
365
378
  fn = method_function_name(spec, method)
366
379
  ret = ffi_return_to_cpp(method[:ffi_return])
367
380
  lines << "extern \"C\" #{ret} #{fn}(#{cpp_method_signature(method).join(', ')}) {"
368
- lines << ' if (!handle) {'
369
- lines << cpp_null_handle_return(method)
370
- lines << ' }'
381
+ unless method[:static]
382
+ lines << ' if (!handle) {'
383
+ lines << cpp_null_handle_return(method)
384
+ lines << ' }'
385
+ end
371
386
  lines << ''
372
- lines << " auto* self_obj = static_cast<#{spec[:qt_class]}*>(handle);"
373
- emit_cpp_method_return(lines, method, cpp_method_invocation(method))
387
+ lines << " auto* self_obj = static_cast<#{spec[:qt_class]}*>(handle);" unless method[:static]
388
+ if method[:dispatch]
389
+ lines << ' switch (argc) {'
390
+ (method[:required_arg_count]..method[:args].length).each do |arg_count|
391
+ selected = method.merge(args: method[:args].take(arg_count))
392
+ lines << " case #{arg_count}: {"
393
+ emit_cpp_method_return(lines, selected, cpp_method_invocation(selected))
394
+ lines << ' break;' if method[:ffi_return] == :void
395
+ lines << ' }'
396
+ end
397
+ lines << ' default:'
398
+ lines << cpp_null_handle_return(method).sub(/^ /, ' ')
399
+ lines << ' }'
400
+ lines << cpp_null_handle_return(method) if method[:ffi_return] != :void
401
+ else
402
+ emit_cpp_method_return(lines, method, cpp_method_invocation(method))
403
+ end
374
404
  lines << '}'
375
405
  end
376
406
 
@@ -381,11 +411,13 @@ def generate_cpp_bridge(specs, free_function_specs)
381
411
 
382
412
  specs.each { |spec| append_cpp_spec_methods(lines, spec) }
383
413
 
414
+ generate_cpp_qt_value_deletes(lines, specs, free_function_specs)
384
415
  generate_cpp_delete(lines)
385
416
  "#{lines.join("\n")}\n"
386
417
  end
387
418
 
388
419
  def append_cpp_spec_methods(lines, spec)
420
+ generate_cpp_virtual_adapter(lines, spec) if spec[:virtual_adapter]
389
421
  unless spec[:constructor][:mode] == :wrap_only
390
422
  generate_cpp_constructor(lines, spec)
391
423
  lines << ''
@@ -407,6 +439,8 @@ def cpp_bridge_prelude
407
439
  #include <QAnyStringView>
408
440
  #include <QIcon>
409
441
  #include <QJsonDocument>
442
+ #include <QJsonArray>
443
+ #include <QJsonObject>
410
444
  #include <QJsonParseError>
411
445
  #include <QDateTime>
412
446
  #include <QDate>
@@ -553,6 +587,14 @@ def cpp_bridge_prelude
553
587
  return QStringLiteral("qtv:str:") + QString::fromUtf8(fallback);
554
588
  }
555
589
 
590
+ QString qstring_list_to_bridge_string(const QStringList& value) {
591
+ QJsonArray array;
592
+ for (const QString& item : value) {
593
+ array.append(item);
594
+ }
595
+ return QString::fromUtf8(QJsonDocument(array).toJson(QJsonDocument::Compact));
596
+ }
597
+
556
598
  QString qobject_list_to_bridge_string(const QObjectList& value) {
557
599
  QJsonArray array;
558
600
  for (QObject* object : value) {
@@ -640,6 +682,7 @@ def optional_arg_replacement(arg, safe)
640
682
  when :bool then "(#{safe}.nil? ? false : #{safe})"
641
683
  when :pointer then safe
642
684
  when :string
685
+ return "(#{safe}.nil? ? Qt::VariantCodec.encode([]) : Qt::VariantCodec.encode(#{safe}))" if arg[:cast] == :qint_list_from_variant
643
686
  return "(#{safe}.nil? ? '' : Qt::VariantCodec.encode(#{safe}))" if arg[:cast] == :qvariant_from_utf8
644
687
  return "(#{safe}.nil? ? '' : Qt::DateTimeCodec.encode_qdatetime(#{safe}))" if arg[:cast] == :qdatetime_from_utf8
645
688
  return "(#{safe}.nil? ? '' : Qt::DateTimeCodec.encode_qdate(#{safe}))" if arg[:cast] == :qdate_from_utf8
@@ -653,8 +696,13 @@ def optional_arg_replacement(arg, safe)
653
696
  end
654
697
 
655
698
  def ruby_arg_call_value(arg, safe, optional:)
699
+ if arg[:cast] == :qt_value_ref
700
+ return optional ? "(#{safe}.nil? ? nil : #{safe}.handle)" : "#{safe}.handle"
701
+ end
702
+
656
703
  return "Qt::StringCodec.to_qt_text(#{safe})" if text_bridge_arg?(arg) && !optional
657
704
  return "Qt::VariantCodec.encode(#{safe})" if arg[:cast] == :qvariant_from_utf8 && !optional
705
+ return "Qt::VariantCodec.encode(#{safe})" if arg[:cast] == :qint_list_from_variant && !optional
658
706
  return "Qt::DateTimeCodec.encode_qdatetime(#{safe})" if arg[:cast] == :qdatetime_from_utf8 && !optional
659
707
  return "Qt::DateTimeCodec.encode_qdate(#{safe})" if arg[:cast] == :qdate_from_utf8 && !optional
660
708
  return "Qt::DateTimeCodec.encode_qtime(#{safe})" if arg[:cast] == :qtime_from_utf8 && !optional
@@ -687,20 +735,26 @@ def append_ruby_native_call_method(lines, method:, native_call:, indent:)
687
735
  method_body = ruby_native_method_body(method, rewritten_native_call)
688
736
 
689
737
  lines << "#{indent}def #{ruby_name}(#{ruby_args})"
690
- lines << "#{indent} #{method_body}"
738
+ lines << "#{indent} Qt::VirtualMethodRuntime.after_native_call(#{method_body})"
691
739
  lines << "#{indent}end"
692
740
  lines << "#{indent}alias_method :#{snake_alias}, :#{ruby_name}" if snake_alias != ruby_name
693
741
  end
694
742
 
695
743
  def ruby_native_method_body(method, rewritten_native_call)
696
744
  return "Qt::StringCodec.from_qt_text(#{rewritten_native_call})" if method[:return_cast] == :qstring_to_utf8
745
+ return "JSON.parse(Qt::StringCodec.from_qt_text(#{rewritten_native_call}))" if method[:return_cast] == :qstring_list_to_json
697
746
  if method[:return_cast] == :qobject_list_to_wrapped_array
698
747
  return "Qt::ObjectListCodec.decode(#{rewritten_native_call}, '#{method[:object_list_class]}')"
699
748
  end
700
749
  return "Qt::VariantCodec.decode(#{rewritten_native_call})" if method[:return_cast] == :qvariant_to_utf8
750
+ return "Qt::VariantCodec.decode(#{rewritten_native_call})" if method[:return_cast] == :qint_list_to_variant
701
751
  return "Qt::DateTimeCodec.decode_qdatetime(#{rewritten_native_call})" if method[:return_cast] == :qdatetime_to_utf8
702
752
  return "Qt::DateTimeCodec.decode_qdate(#{rewritten_native_call})" if method[:return_cast] == :qdate_to_utf8
703
753
  return "Qt::DateTimeCodec.decode_qtime(#{rewritten_native_call})" if method[:return_cast] == :qtime_to_utf8
754
+ if method[:return_cast] == :qt_value_copy
755
+ delete_method = qt_value_delete_function_name(method[:value_class])
756
+ return "Qt::ValueWrapper.wrap(#{rewritten_native_call}, '#{method[:value_class]}', :#{delete_method})"
757
+ end
704
758
  return "Qt::ObjectWrapper.wrap(#{rewritten_native_call}, '#{method[:pointer_class]}')" if method[:pointer_class]
705
759
 
706
760
  rewritten_native_call
@@ -765,6 +819,8 @@ def append_widget_initializer(lines, spec:, widget_root:, indent:)
765
819
  append_default_widget_initializer(lines, spec, indent)
766
820
  end
767
821
 
822
+ lines << "#{indent} Qt::VirtualMethodRuntime.attach(self)" if spec[:virtual_adapter]
823
+
768
824
  lines << "#{indent} yield self if block_given?"
769
825
  lines << "#{indent}end"
770
826
  end
@@ -777,22 +833,32 @@ end
777
833
 
778
834
  def append_parent_widget_initializer(lines, spec, widget_root, indent)
779
835
  lines << "#{indent}def initialize(parent = nil)"
836
+ append_virtual_runtime_install(lines, spec, indent)
780
837
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(parent&.handle)"
781
838
  append_parent_registration_logic(lines, spec, indent)
782
839
  end
783
840
 
784
841
  def append_default_widget_initializer(lines, spec, indent)
785
842
  lines << "#{indent}def initialize(_argc = 0, _argv = [])"
843
+ append_virtual_runtime_install(lines, spec, indent)
786
844
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new"
787
845
  end
788
846
 
789
847
  def append_string_path_initializer(lines, spec, indent)
790
848
  lines << "#{indent}def initialize(path = nil)"
849
+ append_virtual_runtime_install(lines, spec, indent)
791
850
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(Qt::StringCodec.to_qt_text(path))"
792
851
  end
793
852
 
853
+ def append_virtual_runtime_install(lines, spec, indent)
854
+ return unless spec[:virtual_adapter]
855
+
856
+ lines << "#{indent} Qt::VirtualMethodRuntime.ensure_installed!"
857
+ end
858
+
794
859
  def append_keysequence_parent_initializer(lines, spec, widget_root, indent)
795
860
  lines << "#{indent}def initialize(key = nil, parent = nil)"
861
+ append_virtual_runtime_install(lines, spec, indent)
796
862
  lines << "#{indent} if parent.nil? && (key.nil? || key.respond_to?(:handle))"
797
863
  lines << "#{indent} parent = key"
798
864
  lines << "#{indent} key = nil"
@@ -810,8 +876,9 @@ def append_parent_registration_logic(lines, spec, indent)
810
876
  end
811
877
  end
812
878
 
813
- def append_ruby_qapplication_prelude(lines, spec, metadata)
814
- lines << ' class QApplication'
879
+ def append_ruby_qapplication_prelude(lines, spec, metadata, super_ruby)
880
+ superclass = super_ruby ? " < #{super_ruby}" : ''
881
+ lines << " class QApplication#{superclass}"
815
882
  append_ruby_class_api_constants(lines, qt_class: spec[:qt_class], metadata: metadata, indent: ' ')
816
883
  lines << ''
817
884
  lines << ' attr_reader :handle'
@@ -831,7 +898,7 @@ def append_ruby_qapplication_singleton_accessors(lines)
831
898
  lines << ' end'
832
899
  end
833
900
 
834
- def generate_ruby_qapplication(lines, spec)
901
+ def generate_ruby_qapplication(lines, spec, super_ruby:)
835
902
  metadata = ruby_api_metadata(spec[:methods])
836
903
  metadata[:singleton_setter_aliases] = Array(spec[:class_methods]).each_with_object({}) do |method, aliases|
837
904
  aliases.merge!(setter_alias_specs(method))
@@ -841,7 +908,8 @@ def generate_ruby_qapplication(lines, spec)
841
908
  metadata[:singleton_setter_aliases]
842
909
  )
843
910
 
844
- append_ruby_qapplication_prelude(lines, spec, metadata)
911
+ append_ruby_qapplication_prelude(lines, spec, metadata, super_ruby)
912
+ append_ruby_widget_methods(lines, spec.merge(methods: spec[:methods].reject { |method| method[:qt_name] == 'exec' }))
845
913
  append_ruby_qapplication_singleton_accessors(lines)
846
914
 
847
915
  Array(spec[:class_methods]).each { |method| append_ruby_qapplication_class_method(lines, method) }
@@ -893,12 +961,18 @@ end
893
961
 
894
962
  def qapplication_class_method_body(method, native_call)
895
963
  return " Qt::StringCodec.from_qt_text(#{native_call})" if method[:return_cast] == :qstring_to_utf8
964
+ return " JSON.parse(Qt::StringCodec.from_qt_text(#{native_call}))" if method[:return_cast] == :qstring_list_to_json
896
965
  if method[:return_cast] == :qobject_list_to_wrapped_array
897
966
  return " Qt::ObjectListCodec.decode(#{native_call}, '#{method[:object_list_class]}')"
898
967
  end
899
968
  return " Qt::DateTimeCodec.decode_qdatetime(#{native_call})" if method[:return_cast] == :qdatetime_to_utf8
900
969
  return " Qt::DateTimeCodec.decode_qdate(#{native_call})" if method[:return_cast] == :qdate_to_utf8
901
970
  return " Qt::DateTimeCodec.decode_qtime(#{native_call})" if method[:return_cast] == :qtime_to_utf8
971
+ return " Qt::VariantCodec.decode(#{native_call})" if method[:return_cast] == :qint_list_to_variant
972
+ if method[:return_cast] == :qt_value_copy
973
+ delete_method = qt_value_delete_function_name(method[:value_class])
974
+ return " Qt::ValueWrapper.wrap(#{native_call}, '#{method[:value_class]}', :#{delete_method})"
975
+ end
902
976
  return " Qt::ObjectWrapper.wrap(#{native_call}, '#{method[:pointer_class]}')" if method[:pointer_class]
903
977
 
904
978
  " #{native_call}"
@@ -918,26 +992,303 @@ def generate_ruby_widget_class_header(lines, spec, metadata:, super_ruby:, class
918
992
  end
919
993
 
920
994
  def append_ruby_widget_methods(lines, spec)
921
- spec[:methods].each do |method|
922
- call_args = ['@handle'] + method[:args].map { |arg| arg[:name] }
923
- native_name = method_function_name(spec, method).delete_prefix('qt_ruby_')
924
- native_call = "Native.#{native_name}(#{call_args.join(', ')})"
925
- append_ruby_native_call_method(lines, method: method, native_call: native_call, indent: ' ')
926
- append_ruby_property_writer(lines, method: method, indent: ' ')
995
+ instance_groups = spec[:methods].reject { |method| method[:static] }.group_by { |method| method[:ruby_name] }
996
+ instance_groups.each_value do |methods|
997
+ if methods.one? && !methods.first[:dispatch]
998
+ method = methods.first
999
+ call_args = ['@handle'] + method[:args].map { |arg| arg[:name] }
1000
+ native_name = method_function_name(spec, method).delete_prefix('qt_ruby_')
1001
+ append_ruby_native_call_method(
1002
+ lines,
1003
+ method: method,
1004
+ native_call: "Native.#{native_name}(#{call_args.join(', ')})",
1005
+ indent: ' '
1006
+ )
1007
+ append_ruby_property_writer(lines, method: method, indent: ' ')
1008
+ else
1009
+ append_ruby_overload_dispatcher(lines, methods, spec: spec, target: :instance, indent: ' ')
1010
+ end
1011
+ lines << ''
1012
+ end
1013
+
1014
+ static_groups = spec[:methods].select { |method| method[:static] }.group_by { |method| method[:ruby_name] }
1015
+ return if static_groups.empty?
1016
+
1017
+ lines << ' class << self'
1018
+ static_groups.each_value do |methods|
1019
+ if methods.one? && !methods.first[:dispatch]
1020
+ method = methods.first
1021
+ native_name = method_function_name(spec, method).delete_prefix('qt_ruby_')
1022
+ append_ruby_native_call_method(
1023
+ lines,
1024
+ method: method,
1025
+ native_call: "Native.#{native_name}(nil, #{method[:args].map { |arg| arg[:name] }.join(', ')})",
1026
+ indent: ' '
1027
+ )
1028
+ else
1029
+ append_ruby_overload_dispatcher(lines, methods, spec: spec, target: :class, indent: ' ')
1030
+ end
927
1031
  lines << ''
928
1032
  end
1033
+ lines << ' end'
1034
+ lines << ''
1035
+ end
1036
+
1037
+ def append_ruby_overload_dispatcher(lines, methods, spec:, target:, indent:)
1038
+ first = methods.first
1039
+ ruby_name = ruby_safe_method_name(first[:ruby_name])
1040
+ snake_alias = to_snake(ruby_name)
1041
+ max_args = methods.map { |method| method[:args].length }.max
1042
+ min_required = methods.map { |method| method[:required_arg_count] }.min
1043
+ arg_names = (1..max_args).map { |index| "qt_arg#{index}" }
1044
+ signature = arg_names.each_with_index.map do |arg_name, index|
1045
+ index < min_required ? arg_name : "#{arg_name} = Qt::GeneratedOverloadRuntime::OMITTED"
1046
+ end.join(', ')
1047
+
1048
+ lines << "#{indent}def #{ruby_name}(#{signature})"
1049
+ lines << "#{indent} args = [#{arg_names.join(', ')}]"
1050
+ lines << "#{indent} args.pop while args.last.equal?(Qt::GeneratedOverloadRuntime::OMITTED)"
1051
+
1052
+ methods.sort_by { |method| [-method[:args].count { |arg| overload_arg_specificity(arg).positive? }, -method[:required_arg_count], method[:args].length] }
1053
+ .each do |method|
1054
+ predicates = [
1055
+ "args.length >= #{method[:required_arg_count]}",
1056
+ "args.length <= #{method[:args].length}"
1057
+ ]
1058
+ method[:args].each_with_index do |arg, index|
1059
+ predicates << overload_arg_predicate(arg, "args[#{index}]", optional: index >= method[:required_arg_count])
1060
+ end
1061
+ lines << "#{indent} if #{predicates.join(' && ')}"
1062
+
1063
+ arg_map = ruby_arg_name_map(method[:args])
1064
+ method[:args].each_with_index do |arg, index|
1065
+ lines << "#{indent} #{arg_map[arg[:name]]} = args[#{index}]"
1066
+ end
1067
+ native_name = method_function_name(spec, method).delete_prefix('qt_ruby_')
1068
+ native_args = [target == :instance ? '@handle' : 'nil']
1069
+ native_args << 'args.length' if method[:dispatch]
1070
+ native_args.concat(method[:args].map { |arg| arg_map[arg[:name]] })
1071
+ native_call = "Native.#{native_name}(#{native_args.join(', ')})"
1072
+ required_arg_count = method.fetch(:required_arg_count, method[:args].length)
1073
+ rewritten_native_call = rewrite_native_call_args(native_call, method, arg_map, required_arg_count)
1074
+ lines << "#{indent} return Qt::VirtualMethodRuntime.after_native_call(#{ruby_native_method_body(method, rewritten_native_call)})"
1075
+ lines << "#{indent} end"
1076
+ end
1077
+
1078
+ lines << "#{indent} raise ArgumentError, \"no matching overload for #{ruby_name} (given \" + args.length.to_s + \" arguments)\""
1079
+ lines << "#{indent}end"
1080
+ lines << "#{indent}alias_method :#{snake_alias}, :#{ruby_name}" if snake_alias != ruby_name
1081
+ end
1082
+
1083
+ def overload_arg_specificity(arg)
1084
+ return 3 if arg[:cast] == :qt_value_ref || arg[:cast] == :qicon_ref
1085
+ return 2 if arg[:cast].is_a?(String) && arg[:cast].end_with?('*')
1086
+
1087
+ 0
1088
+ end
1089
+
1090
+ def overload_arg_predicate(arg, expression, optional:)
1091
+ prefix = optional ? "(#{expression}.nil? || " : ''
1092
+ suffix = optional ? ')' : ''
1093
+ predicate =
1094
+ if arg[:cast] == :qt_value_ref || arg[:cast] == :qicon_ref
1095
+ class_name = arg[:value_class] || ('QIcon' if arg[:cast] == :qicon_ref)
1096
+ class_name ? "#{expression}.is_a?(Qt::#{class_name})" : "#{expression}.respond_to?(:handle)"
1097
+ elsif arg[:cast].is_a?(String) && arg[:cast].end_with?('*')
1098
+ class_name = arg[:cast].delete_suffix('*').split('::').last
1099
+ if class_name.match?(/\AQ[A-Z]\w*\z/)
1100
+ "#{expression}.is_a?(Qt::#{class_name})"
1101
+ else
1102
+ "#{expression}.respond_to?(:handle)"
1103
+ end
1104
+ else
1105
+ case arg[:ffi]
1106
+ when :string then "#{expression}.is_a?(String) || #{expression}.respond_to?(:to_str)"
1107
+ when :int then "#{expression}.is_a?(Integer)"
1108
+ when :bool then "#{expression} == true || #{expression} == false"
1109
+ when :pointer then "#{expression}.nil? || #{expression}.respond_to?(:handle)"
1110
+ else 'true'
1111
+ end
1112
+ end
1113
+ "#{prefix}(#{predicate})#{suffix}"
929
1114
  end
930
1115
 
931
1116
  def assign_unique_native_method_names(specs)
932
1117
  used = Set.new
933
1118
  specs.map do |spec|
934
- methods = spec[:methods].map do |method|
1119
+ method_groups = spec[:methods].group_by { |method| method[:ruby_name] }
1120
+ methods = spec[:methods].map do |original|
1121
+ method = original.merge(dispatch: method_groups[original[:ruby_name]].length > 1 ||
1122
+ original.fetch(:required_arg_count, original[:args].length) < original[:args].length)
935
1123
  assign_unique_native_method_name(spec, method, used)
936
1124
  end
937
1125
  spec.merge(methods: methods)
938
1126
  end
939
1127
  end
940
1128
 
1129
+ def virtual_wire_argument(arg)
1130
+ return { kind: :integer } if arg[:ffi] == :int
1131
+ return { kind: :boolean } if arg[:ffi] == :bool
1132
+ return { kind: :string } if arg[:ffi] == :string && arg[:cast] == :qstring
1133
+ return { kind: :value, qt_class: arg[:value_class] } if arg[:cast] == :qt_value_ref
1134
+ return { kind: :value, qt_class: 'QIcon' } if arg[:cast] == :qicon_ref
1135
+
1136
+ if arg[:ffi] == :pointer && arg[:cast].is_a?(String) && arg[:cast].end_with?('*')
1137
+ qt_class = arg[:cast].delete_suffix('*').split('::').last
1138
+ return { kind: :pointer, qt_class: qt_class } if qt_class.match?(/\AQ[A-Z]\w*\z/)
1139
+ end
1140
+ nil
1141
+ end
1142
+
1143
+ def virtual_wire_return(method)
1144
+ return { kind: :void } if method[:ffi_return] == :void
1145
+ return { kind: :integer } if method[:ffi_return] == :int
1146
+ return { kind: :boolean } if method[:ffi_return] == :bool
1147
+ return { kind: :string } if method[:ffi_return] == :string && method[:return_cast] == :qstring_to_utf8
1148
+ return { kind: :value, qt_class: method[:value_class] } if method[:return_cast] == :qt_value_copy
1149
+ return { kind: :pointer, qt_class: method[:pointer_class] } if method[:ffi_return] == :pointer && method[:pointer_class]
1150
+
1151
+ nil
1152
+ end
1153
+
1154
+ def virtual_method_wire_spec(method)
1155
+ return nil unless method[:virtual] && !method[:static] && !method[:final]
1156
+ return nil if method[:qt_name].start_with?('qt_') || forbidden_auto_exportable_method_name?(method[:qt_name])
1157
+ return nil unless method[:args].length == method[:cpp_params].length
1158
+
1159
+ args = method[:args].map { |arg| virtual_wire_argument(arg) }
1160
+ result = virtual_wire_return(method)
1161
+ return nil if args.any?(&:nil?) || result.nil?
1162
+
1163
+ { args: args, return: result }
1164
+ end
1165
+
1166
+ def assign_virtual_method_ids(specs, ast:)
1167
+ next_id = 1
1168
+ class_index = ast_class_index(ast)
1169
+ specs.map do |spec|
1170
+ constructor_mode = spec.dig(:constructor, :mode)
1171
+ adapter_available = constructor_mode != :wrap_only && constructor_mode != :qapplication &&
1172
+ !class_index[:abstract_by_class][spec[:qt_class]] &&
1173
+ !class_index[:final_by_class][spec[:qt_class]]
1174
+ methods = spec[:methods].map do |method|
1175
+ wire_spec = virtual_method_wire_spec(method)
1176
+ next method unless adapter_available && wire_spec
1177
+
1178
+ result = method.merge(virtual_method_id: next_id, virtual_wire: wire_spec)
1179
+ next_id += 1
1180
+ result
1181
+ end
1182
+ spec.merge(
1183
+ methods: methods,
1184
+ virtual_adapter: adapter_available && methods.any? { |method| method[:virtual_method_id] }
1185
+ )
1186
+ end
1187
+ end
1188
+
1189
+ def generated_virtual_subclass_name(spec)
1190
+ "QtRubyGenerated#{spec[:qt_class].gsub(/[^A-Za-z0-9_]/, '_')}Override"
1191
+ end
1192
+
1193
+ def cpp_virtual_argument_wire_lines(arg, index)
1194
+ expression = "arg#{index + 1}"
1195
+ wire = virtual_wire_argument(arg)
1196
+ case wire&.dig(:kind)
1197
+ when :integer then ["arguments.append(static_cast<int>(#{expression}));"]
1198
+ when :boolean then ["arguments.append(#{expression});"]
1199
+ when :string then ["arguments.append(#{expression});"]
1200
+ when :pointer, :value
1201
+ pointer_expr = wire[:kind] == :pointer ? expression : "&#{expression}"
1202
+ [
1203
+ "if (#{pointer_expr}) {",
1204
+ " arguments.append(QString::number(static_cast<qulonglong>(reinterpret_cast<quintptr>(#{pointer_expr}))));",
1205
+ '} else {',
1206
+ ' arguments.append(QJsonValue(QJsonValue::Null));',
1207
+ '}'
1208
+ ]
1209
+ else
1210
+ []
1211
+ end
1212
+ end
1213
+
1214
+ def cpp_virtual_fallback_lines(method, spec, arg_names, indent: ' ')
1215
+ invocation = "#{spec[:qt_class]}::#{method[:qt_name]}(#{arg_names.join(', ')})"
1216
+ if method[:ffi_return] == :void
1217
+ ["#{indent}#{invocation};", "#{indent}return;"]
1218
+ else
1219
+ ["#{indent}return #{invocation};"]
1220
+ end
1221
+ end
1222
+
1223
+ def cpp_virtual_response_lines(method, indent: ' ')
1224
+ wire_return = method.dig(:virtual_wire, :return)
1225
+ value = 'response_object.value(QStringLiteral("value"))'
1226
+ case wire_return[:kind]
1227
+ when :void
1228
+ ["#{indent}return;"]
1229
+ when :integer
1230
+ ["#{indent}return static_cast<#{method[:cpp_return_type]}>(#{value}.toInt());"]
1231
+ when :boolean
1232
+ ["#{indent}return #{value}.toBool();"]
1233
+ when :string
1234
+ ["#{indent}return #{value}.toString();"]
1235
+ when :pointer
1236
+ [
1237
+ "#{indent}const quintptr address = static_cast<quintptr>(#{value}.toString().toULongLong());",
1238
+ "#{indent}return reinterpret_cast<#{method[:cpp_return_type]}>(address);"
1239
+ ]
1240
+ when :value
1241
+ [
1242
+ "#{indent}const quintptr address = static_cast<quintptr>(#{value}.toString().toULongLong());",
1243
+ "#{indent}if (address == 0) return #{method[:cpp_return_type]}();",
1244
+ "#{indent}return *reinterpret_cast<#{method[:cpp_return_type]}*>(address);"
1245
+ ]
1246
+ else
1247
+ []
1248
+ end
1249
+ end
1250
+
1251
+ def generate_cpp_virtual_adapter(lines, spec)
1252
+ methods = spec[:methods].select { |method| method[:virtual_method_id] }
1253
+ return if methods.empty?
1254
+
1255
+ adapter_name = generated_virtual_subclass_name(spec)
1256
+ lines << "class #{adapter_name} final : public #{spec[:qt_class]} {"
1257
+ lines << 'public:'
1258
+ lines << " using #{spec[:qt_class]}::#{spec[:qt_class]};"
1259
+ lines << " ~#{adapter_name}() { QtRubyRuntime::set_virtual_dispatch_enabled(this, 0); }"
1260
+ lines << ''
1261
+ methods.each do |method|
1262
+ arg_names = method[:args].each_index.map { |index| "arg#{index + 1}" }
1263
+ cpp_params = method[:cpp_params].each_with_index.map { |type, index| "#{type} #{arg_names[index]}" }
1264
+ const_qualifier = method[:cpp_const] ? ' const' : ''
1265
+ lines << " #{method[:cpp_return_type]} #{method[:qt_name]}(#{cpp_params.join(', ')})#{const_qualifier} override {"
1266
+ lines << " if (!QtRubyRuntime::virtual_dispatch_enabled(const_cast<#{adapter_name}*>(this))) {"
1267
+ lines.concat(cpp_virtual_fallback_lines(method, spec, arg_names, indent: ' '))
1268
+ lines << ' }'
1269
+ lines << " if (QtRubyRuntime::ruby_virtual_method_active(this, #{method[:virtual_method_id]})) {"
1270
+ lines.concat(cpp_virtual_fallback_lines(method, spec, arg_names))
1271
+ lines << ' }'
1272
+ lines << ' QJsonArray arguments;'
1273
+ method[:args].each_with_index do |arg, index|
1274
+ cpp_virtual_argument_wire_lines(arg, index).each { |line| lines << " #{line}" }
1275
+ end
1276
+ lines << ' const QByteArray payload = QJsonDocument(arguments).toJson(QJsonDocument::Compact);'
1277
+ lines << " const QByteArray response = QtRubyRuntime::dispatch_virtual_method(this, #{method[:virtual_method_id]}, payload);"
1278
+ lines << ' QJsonParseError response_error;'
1279
+ lines << ' const QJsonDocument response_document = QJsonDocument::fromJson(response, &response_error);'
1280
+ lines << ' const QJsonObject response_object = response_document.object();'
1281
+ lines << ' if (response_error.error == QJsonParseError::NoError && response_object.value(QStringLiteral("handled")).toBool()) {'
1282
+ lines.concat(cpp_virtual_response_lines(method))
1283
+ lines << ' }'
1284
+ lines.concat(cpp_virtual_fallback_lines(method, spec, arg_names, indent: ' '))
1285
+ lines << ' }'
1286
+ lines << ''
1287
+ end
1288
+ lines << '};'
1289
+ lines << ''
1290
+ end
1291
+
941
1292
  def assign_unique_native_method_name(spec, method, used)
942
1293
  native_name = default_method_function_name(spec, method)
943
1294
  return used_add_and_return(used, native_name, method) unless used.include?(native_name)
@@ -1039,16 +1390,35 @@ end
1039
1390
 
1040
1391
  def append_ruby_widgets_classes(lines, specs, super_qt_by_qt, wrapper_qt_classes)
1041
1392
  qapplication_spec = specs.find { |spec| spec[:ruby_class] == 'QApplication' }
1042
- generate_ruby_qapplication(lines, qapplication_spec)
1043
-
1044
1393
  specs_by_qt = specs.each_with_object({}) { |spec, map| map[spec[:qt_class]] = spec }
1045
1394
  qt_to_ruby = build_qt_to_ruby_map(specs, wrapper_qt_classes)
1046
1395
  emitted_qts = qts_to_emit(specs, wrapper_qt_classes)
1047
1396
  emit_qt_classes(lines, emitted_qts, specs_by_qt, super_qt_by_qt, qt_to_ruby)
1397
+ generate_ruby_qapplication(
1398
+ lines,
1399
+ qapplication_spec,
1400
+ super_ruby: qt_to_ruby[super_qt_by_qt['QApplication']]
1401
+ )
1048
1402
  end
1049
1403
 
1050
1404
  def generate_ruby_widgets(specs, super_qt_by_qt, wrapper_qt_classes)
1051
1405
  lines = ruby_widgets_prelude_lines
1406
+ descriptors = specs.flat_map do |spec|
1407
+ spec[:methods].filter_map do |method|
1408
+ next unless method[:virtual_method_id]
1409
+
1410
+ {
1411
+ id: method[:virtual_method_id],
1412
+ wrapper_class: spec[:ruby_class],
1413
+ ruby_name: ruby_safe_method_name(method[:ruby_name]),
1414
+ snake_name: to_snake(ruby_safe_method_name(method[:ruby_name])),
1415
+ args: method[:virtual_wire][:args],
1416
+ return: method[:virtual_wire][:return]
1417
+ }
1418
+ end
1419
+ end
1420
+ lines << " GENERATED_VIRTUAL_METHODS = #{descriptors.inspect}.freeze"
1421
+ lines << '' unless descriptors.empty?
1052
1422
  append_ruby_widgets_classes(lines, specs, super_qt_by_qt, wrapper_qt_classes)
1053
1423
 
1054
1424
  lines << 'end'
@@ -1173,6 +1543,7 @@ def collect_qt_scoped_enum_constants(ast, warnings = [])
1173
1543
  next unless owner.match?(/\AQ[A-Z]\w*\z/)
1174
1544
  next if owner == 'Qt' || owner == 'QEvent'
1175
1545
 
1546
+ next_value = 0
1176
1547
  Array(node['inner']).each do |entry|
1177
1548
  next unless entry['kind'] == 'EnumConstantDecl'
1178
1549
 
@@ -1181,7 +1552,13 @@ def collect_qt_scoped_enum_constants(ast, warnings = [])
1181
1552
 
1182
1553
  raw_value = ast_extract_first_value(entry)
1183
1554
  value = parse_ast_integer_value(raw_value)
1184
- next if value.nil?
1555
+ value = next_value if value.nil? && !next_value.nil?
1556
+ if value.nil?
1557
+ next_value = nil
1558
+ next
1559
+ end
1560
+
1561
+ next_value = value + 1
1185
1562
 
1186
1563
  append_constant_with_conflict_warning(
1187
1564
  constants_by_owner[owner],
@@ -1240,7 +1617,9 @@ base_specs = timed('build_base_specs') { build_base_specs(ast) }
1240
1617
  timed('validate_qt_api') { validate_qt_api!(ast, base_specs) }
1241
1618
  expanded_specs = timed('expand_auto_methods') { expand_auto_methods(base_specs, ast) }
1242
1619
  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) }
1620
+ effective_specs = timed('assign_unique_native_method_names') do
1621
+ assign_virtual_method_ids(assign_unique_native_method_names(property_specs), ast: ast)
1622
+ end
1244
1623
  super_qt_by_qt, wrapper_qt_classes = timed('build_generated_inheritance') do
1245
1624
  build_generated_inheritance(ast, effective_specs)
1246
1625
  end