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.
- checksums.yaml +4 -4
- data/ext/qt_ruby_bridge/extconf.rb +1 -0
- data/ext/qt_ruby_bridge/qt_ruby_runtime.hpp +8 -0
- data/ext/qt_ruby_bridge/runtime_virtual_methods.cpp +78 -0
- data/lib/qt/application_lifecycle.rb +1 -1
- data/lib/qt/generated_overload_runtime.rb +7 -0
- data/lib/qt/object_wrapper.rb +1 -0
- data/lib/qt/version.rb +1 -1
- data/lib/qt/virtual_method_runtime.rb +165 -0
- data/lib/qt.rb +3 -0
- data/scripts/generate_bridge/ast_introspection.rb +16 -3
- data/scripts/generate_bridge/auto_methods.rb +171 -20
- data/scripts/generate_bridge/cpp_method_return_emitter.rb +15 -0
- data/scripts/generate_bridge/ffi_api.rb +1 -1
- data/scripts/generate_bridge/free_function_specs.rb +0 -158
- data/scripts/generate_bridge/spec_discovery.rb +51 -12
- data/scripts/generate_bridge.rb +374 -30
- metadata +4 -1
data/scripts/generate_bridge.rb
CHANGED
|
@@ -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,
|
|
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,
|
|
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[:
|
|
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]}
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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].
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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
|
-
|
|
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')
|
|
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.
|
|
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
|