qt 0.2.0 → 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 = {
@@ -308,26 +307,27 @@ end
308
307
 
309
308
  def generate_cpp_constructor(lines, spec)
310
309
  name = ctor_function_name(spec)
310
+ qt_class = spec[:virtual_adapter] ? generated_virtual_subclass_name(spec) : spec[:qt_class]
311
311
 
312
312
  if spec[:constructor][:mode] == :qapplication
313
313
  emit_cpp_qapplication_constructor(lines, name)
314
314
  return
315
315
  end
316
316
  if spec[:constructor][:mode] == :string_path
317
- 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])
318
318
  return
319
319
  end
320
320
  if spec[:constructor][:mode] == :keysequence_parent
321
- emit_cpp_keysequence_parent_constructor(lines, name, spec)
321
+ emit_cpp_keysequence_parent_constructor(lines, name, spec.merge(qt_class: qt_class))
322
322
  return
323
323
  end
324
324
 
325
325
  unless spec[:constructor][:parent]
326
- emit_cpp_default_constructor(lines, name, spec[:qt_class])
326
+ emit_cpp_default_constructor(lines, name, qt_class)
327
327
  return
328
328
  end
329
329
 
330
- emit_cpp_parent_constructor(lines, name, spec)
330
+ emit_cpp_parent_constructor(lines, name, spec.merge(qt_class: qt_class))
331
331
  end
332
332
 
333
333
  def generate_cpp_delete(lines)
@@ -347,7 +347,8 @@ def generate_cpp_qt_value_deletes(lines, specs, free_function_specs)
347
347
  end
348
348
 
349
349
  def cpp_method_signature(method)
350
- ['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]}" }
351
352
  end
352
353
 
353
354
  def cpp_null_handle_return(method)
@@ -369,19 +370,37 @@ end
369
370
 
370
371
  def cpp_method_invocation(method)
371
372
  call_args = method[:args].map { |arg| arg_expr(arg) }.join(', ')
372
- "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})"
373
375
  end
374
376
 
375
377
  def generate_cpp_method(lines, spec, method)
376
378
  fn = method_function_name(spec, method)
377
379
  ret = ffi_return_to_cpp(method[:ffi_return])
378
380
  lines << "extern \"C\" #{ret} #{fn}(#{cpp_method_signature(method).join(', ')}) {"
379
- lines << ' if (!handle) {'
380
- lines << cpp_null_handle_return(method)
381
- lines << ' }'
381
+ unless method[:static]
382
+ lines << ' if (!handle) {'
383
+ lines << cpp_null_handle_return(method)
384
+ lines << ' }'
385
+ end
382
386
  lines << ''
383
- lines << " auto* self_obj = static_cast<#{spec[:qt_class]}*>(handle);"
384
- 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
385
404
  lines << '}'
386
405
  end
387
406
 
@@ -398,6 +417,7 @@ def generate_cpp_bridge(specs, free_function_specs)
398
417
  end
399
418
 
400
419
  def append_cpp_spec_methods(lines, spec)
420
+ generate_cpp_virtual_adapter(lines, spec) if spec[:virtual_adapter]
401
421
  unless spec[:constructor][:mode] == :wrap_only
402
422
  generate_cpp_constructor(lines, spec)
403
423
  lines << ''
@@ -419,6 +439,8 @@ def cpp_bridge_prelude
419
439
  #include <QAnyStringView>
420
440
  #include <QIcon>
421
441
  #include <QJsonDocument>
442
+ #include <QJsonArray>
443
+ #include <QJsonObject>
422
444
  #include <QJsonParseError>
423
445
  #include <QDateTime>
424
446
  #include <QDate>
@@ -565,6 +587,14 @@ def cpp_bridge_prelude
565
587
  return QStringLiteral("qtv:str:") + QString::fromUtf8(fallback);
566
588
  }
567
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
+
568
598
  QString qobject_list_to_bridge_string(const QObjectList& value) {
569
599
  QJsonArray array;
570
600
  for (QObject* object : value) {
@@ -705,13 +735,14 @@ def append_ruby_native_call_method(lines, method:, native_call:, indent:)
705
735
  method_body = ruby_native_method_body(method, rewritten_native_call)
706
736
 
707
737
  lines << "#{indent}def #{ruby_name}(#{ruby_args})"
708
- lines << "#{indent} #{method_body}"
738
+ lines << "#{indent} Qt::VirtualMethodRuntime.after_native_call(#{method_body})"
709
739
  lines << "#{indent}end"
710
740
  lines << "#{indent}alias_method :#{snake_alias}, :#{ruby_name}" if snake_alias != ruby_name
711
741
  end
712
742
 
713
743
  def ruby_native_method_body(method, rewritten_native_call)
714
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
715
746
  if method[:return_cast] == :qobject_list_to_wrapped_array
716
747
  return "Qt::ObjectListCodec.decode(#{rewritten_native_call}, '#{method[:object_list_class]}')"
717
748
  end
@@ -788,6 +819,8 @@ def append_widget_initializer(lines, spec:, widget_root:, indent:)
788
819
  append_default_widget_initializer(lines, spec, indent)
789
820
  end
790
821
 
822
+ lines << "#{indent} Qt::VirtualMethodRuntime.attach(self)" if spec[:virtual_adapter]
823
+
791
824
  lines << "#{indent} yield self if block_given?"
792
825
  lines << "#{indent}end"
793
826
  end
@@ -800,22 +833,32 @@ end
800
833
 
801
834
  def append_parent_widget_initializer(lines, spec, widget_root, indent)
802
835
  lines << "#{indent}def initialize(parent = nil)"
836
+ append_virtual_runtime_install(lines, spec, indent)
803
837
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(parent&.handle)"
804
838
  append_parent_registration_logic(lines, spec, indent)
805
839
  end
806
840
 
807
841
  def append_default_widget_initializer(lines, spec, indent)
808
842
  lines << "#{indent}def initialize(_argc = 0, _argv = [])"
843
+ append_virtual_runtime_install(lines, spec, indent)
809
844
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new"
810
845
  end
811
846
 
812
847
  def append_string_path_initializer(lines, spec, indent)
813
848
  lines << "#{indent}def initialize(path = nil)"
849
+ append_virtual_runtime_install(lines, spec, indent)
814
850
  lines << "#{indent} @handle = Native.#{spec[:prefix]}_new(Qt::StringCodec.to_qt_text(path))"
815
851
  end
816
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
+
817
859
  def append_keysequence_parent_initializer(lines, spec, widget_root, indent)
818
860
  lines << "#{indent}def initialize(key = nil, parent = nil)"
861
+ append_virtual_runtime_install(lines, spec, indent)
819
862
  lines << "#{indent} if parent.nil? && (key.nil? || key.respond_to?(:handle))"
820
863
  lines << "#{indent} parent = key"
821
864
  lines << "#{indent} key = nil"
@@ -833,8 +876,9 @@ def append_parent_registration_logic(lines, spec, indent)
833
876
  end
834
877
  end
835
878
 
836
- def append_ruby_qapplication_prelude(lines, spec, metadata)
837
- 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}"
838
882
  append_ruby_class_api_constants(lines, qt_class: spec[:qt_class], metadata: metadata, indent: ' ')
839
883
  lines << ''
840
884
  lines << ' attr_reader :handle'
@@ -854,7 +898,7 @@ def append_ruby_qapplication_singleton_accessors(lines)
854
898
  lines << ' end'
855
899
  end
856
900
 
857
- def generate_ruby_qapplication(lines, spec)
901
+ def generate_ruby_qapplication(lines, spec, super_ruby:)
858
902
  metadata = ruby_api_metadata(spec[:methods])
859
903
  metadata[:singleton_setter_aliases] = Array(spec[:class_methods]).each_with_object({}) do |method, aliases|
860
904
  aliases.merge!(setter_alias_specs(method))
@@ -864,7 +908,8 @@ def generate_ruby_qapplication(lines, spec)
864
908
  metadata[:singleton_setter_aliases]
865
909
  )
866
910
 
867
- 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' }))
868
913
  append_ruby_qapplication_singleton_accessors(lines)
869
914
 
870
915
  Array(spec[:class_methods]).each { |method| append_ruby_qapplication_class_method(lines, method) }
@@ -916,6 +961,7 @@ end
916
961
 
917
962
  def qapplication_class_method_body(method, native_call)
918
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
919
965
  if method[:return_cast] == :qobject_list_to_wrapped_array
920
966
  return " Qt::ObjectListCodec.decode(#{native_call}, '#{method[:object_list_class]}')"
921
967
  end
@@ -946,26 +992,303 @@ def generate_ruby_widget_class_header(lines, spec, metadata:, super_ruby:, class
946
992
  end
947
993
 
948
994
  def append_ruby_widget_methods(lines, spec)
949
- spec[:methods].each do |method|
950
- call_args = ['@handle'] + method[:args].map { |arg| arg[:name] }
951
- native_name = method_function_name(spec, method).delete_prefix('qt_ruby_')
952
- native_call = "Native.#{native_name}(#{call_args.join(', ')})"
953
- append_ruby_native_call_method(lines, method: method, native_call: native_call, indent: ' ')
954
- 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
955
1031
  lines << ''
956
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}"
957
1114
  end
958
1115
 
959
1116
  def assign_unique_native_method_names(specs)
960
1117
  used = Set.new
961
1118
  specs.map do |spec|
962
- 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)
963
1123
  assign_unique_native_method_name(spec, method, used)
964
1124
  end
965
1125
  spec.merge(methods: methods)
966
1126
  end
967
1127
  end
968
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
+
969
1292
  def assign_unique_native_method_name(spec, method, used)
970
1293
  native_name = default_method_function_name(spec, method)
971
1294
  return used_add_and_return(used, native_name, method) unless used.include?(native_name)
@@ -1067,16 +1390,35 @@ end
1067
1390
 
1068
1391
  def append_ruby_widgets_classes(lines, specs, super_qt_by_qt, wrapper_qt_classes)
1069
1392
  qapplication_spec = specs.find { |spec| spec[:ruby_class] == 'QApplication' }
1070
- generate_ruby_qapplication(lines, qapplication_spec)
1071
-
1072
1393
  specs_by_qt = specs.each_with_object({}) { |spec, map| map[spec[:qt_class]] = spec }
1073
1394
  qt_to_ruby = build_qt_to_ruby_map(specs, wrapper_qt_classes)
1074
1395
  emitted_qts = qts_to_emit(specs, wrapper_qt_classes)
1075
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
+ )
1076
1402
  end
1077
1403
 
1078
1404
  def generate_ruby_widgets(specs, super_qt_by_qt, wrapper_qt_classes)
1079
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?
1080
1422
  append_ruby_widgets_classes(lines, specs, super_qt_by_qt, wrapper_qt_classes)
1081
1423
 
1082
1424
  lines << 'end'
@@ -1275,7 +1617,9 @@ base_specs = timed('build_base_specs') { build_base_specs(ast) }
1275
1617
  timed('validate_qt_api') { validate_qt_api!(ast, base_specs) }
1276
1618
  expanded_specs = timed('expand_auto_methods') { expand_auto_methods(base_specs, ast) }
1277
1619
  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) }
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
1279
1623
  super_qt_by_qt, wrapper_qt_classes = timed('build_generated_inheritance') do
1280
1624
  build_generated_inheritance(ast, effective_specs)
1281
1625
  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.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maksim Veynberg
@@ -48,6 +48,7 @@ files:
48
48
  - ext/qt_ruby_bridge/runtime_events.cpp
49
49
  - ext/qt_ruby_bridge/runtime_signals.cpp
50
50
  - ext/qt_ruby_bridge/runtime_value_types.cpp
51
+ - ext/qt_ruby_bridge/runtime_virtual_methods.cpp
51
52
  - lib/qt.rb
52
53
  - lib/qt/application_lifecycle.rb
53
54
  - lib/qt/bridge.rb
@@ -58,6 +59,7 @@ files:
58
59
  - lib/qt/event_runtime_dispatch.rb
59
60
  - lib/qt/event_runtime_qobject_methods.rb
60
61
  - lib/qt/generated_constants_runtime.rb
62
+ - lib/qt/generated_overload_runtime.rb
61
63
  - lib/qt/generated_setter_aliases_runtime.rb
62
64
  - lib/qt/generated_singleton_forwarders_runtime.rb
63
65
  - lib/qt/inspectable.rb
@@ -70,6 +72,7 @@ files:
70
72
  - lib/qt/value_wrapper.rb
71
73
  - lib/qt/variant_codec.rb
72
74
  - lib/qt/version.rb
75
+ - lib/qt/virtual_method_runtime.rb
73
76
  - scripts/generate_bridge.rb
74
77
  - scripts/generate_bridge/ast_introspection.rb
75
78
  - scripts/generate_bridge/auto_method_spec_resolver.rb