qt 0.1.4 → 0.1.6
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/qt_ruby_runtime.hpp +1 -1
- data/ext/qt_ruby_bridge/runtime_events.cpp +19 -44
- data/lib/qt/event_runtime.rb +17 -11
- data/lib/qt/event_runtime_dispatch.rb +12 -4
- data/lib/qt/version.rb +1 -1
- data/lib/qt.rb +4 -1
- data/scripts/generate_bridge/event_payloads.rb +341 -0
- data/scripts/generate_bridge.rb +44 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58b685a5934b6ab65de8085da77093cf9de7b608a2f047638e6662ece29f4f8a
|
|
4
|
+
data.tar.gz: 3ce44932b8d818b1068908d73189704bdd4b1e2f54e6d2e462c83d217b81c3f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aac76dc37741b86a72be2b8b5f1d671b3f0e4fc59ad95e2958264c7a9929f3dad7d445e3bded8f46d67bc304296b0610d7f427ca19eebaec99fd41dff4d454cb
|
|
7
|
+
data.tar.gz: 50d80970f96868a576ac445b84bbf45ea002bf8c55038b52d3e0c1f2a522d89c6cf2e2120a895b43963cc912900ab63a3520be8d91278d3a8f5a6ec08e364569
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#include <QApplication>
|
|
4
4
|
|
|
5
5
|
namespace QtRubyRuntime {
|
|
6
|
-
using EventCallback =
|
|
6
|
+
using EventCallback = int (*)(void*, int, const char*);
|
|
7
7
|
using SignalCallback = void (*)(void*, int, const char*);
|
|
8
8
|
|
|
9
9
|
// Creates/owns the singleton QApplication used by the Ruby runtime bridge.
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
#include "qt_ruby_runtime.hpp"
|
|
2
2
|
|
|
3
3
|
#include <QApplication>
|
|
4
|
+
#include <QByteArray>
|
|
4
5
|
#include <QCoreApplication>
|
|
5
6
|
#include <QEvent>
|
|
6
7
|
#include <QEventLoop>
|
|
7
|
-
#include <QKeyEvent>
|
|
8
|
-
#include <QMouseEvent>
|
|
9
8
|
#include <QObject>
|
|
10
|
-
#include <QPoint>
|
|
11
|
-
#include <QResizeEvent>
|
|
12
|
-
#include <QByteArray>
|
|
13
9
|
#include <QWidget>
|
|
14
10
|
#include <cstdio>
|
|
15
11
|
#include <cstdlib>
|
|
@@ -17,7 +13,13 @@
|
|
|
17
13
|
#include <unordered_map>
|
|
18
14
|
#include <unordered_set>
|
|
19
15
|
|
|
16
|
+
#include "../../build/generated/event_payloads.inc"
|
|
17
|
+
|
|
20
18
|
namespace QtRubyRuntime {
|
|
19
|
+
constexpr int kEventCallbackIgnore = 0;
|
|
20
|
+
constexpr int kEventCallbackContinue = 1;
|
|
21
|
+
constexpr int kEventCallbackConsume = 2;
|
|
22
|
+
|
|
21
23
|
EventCallback& event_callback_ref() {
|
|
22
24
|
static EventCallback callback = nullptr;
|
|
23
25
|
return callback;
|
|
@@ -117,6 +119,8 @@ const char* event_name(int et) {
|
|
|
117
119
|
return "Leave";
|
|
118
120
|
case QEvent::Resize:
|
|
119
121
|
return "Resize";
|
|
122
|
+
case QEvent::Wheel:
|
|
123
|
+
return "Wheel";
|
|
120
124
|
default:
|
|
121
125
|
return "Other";
|
|
122
126
|
}
|
|
@@ -161,6 +165,7 @@ bool supports_ancestor_dispatch(int event_type) {
|
|
|
161
165
|
case QEvent::FocusOut:
|
|
162
166
|
case QEvent::Enter:
|
|
163
167
|
case QEvent::Leave:
|
|
168
|
+
case QEvent::Wheel:
|
|
164
169
|
return true;
|
|
165
170
|
default:
|
|
166
171
|
return false;
|
|
@@ -219,46 +224,16 @@ class EventFilter : public QObject {
|
|
|
219
224
|
return QObject::eventFilter(watched, event);
|
|
220
225
|
}
|
|
221
226
|
|
|
222
|
-
|
|
223
|
-
int b = 0;
|
|
224
|
-
int c = 0;
|
|
225
|
-
int d = 0;
|
|
226
|
-
|
|
227
|
-
switch (event->type()) {
|
|
228
|
-
case QEvent::MouseButtonPress:
|
|
229
|
-
case QEvent::MouseButtonRelease:
|
|
230
|
-
case QEvent::MouseMove: {
|
|
231
|
-
auto* mouse_event = static_cast<QMouseEvent*>(event);
|
|
232
|
-
const QPoint p = mouse_event->position().toPoint();
|
|
233
|
-
a = p.x();
|
|
234
|
-
b = p.y();
|
|
235
|
-
c = static_cast<int>(mouse_event->button());
|
|
236
|
-
d = static_cast<int>(mouse_event->buttons());
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
case QEvent::KeyPress:
|
|
240
|
-
case QEvent::KeyRelease: {
|
|
241
|
-
auto* key_event = static_cast<QKeyEvent*>(event);
|
|
242
|
-
a = key_event->key();
|
|
243
|
-
b = static_cast<int>(key_event->modifiers());
|
|
244
|
-
c = key_event->isAutoRepeat() ? 1 : 0;
|
|
245
|
-
d = key_event->count();
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
case QEvent::Resize: {
|
|
249
|
-
auto* resize_event = static_cast<QResizeEvent*>(event);
|
|
250
|
-
a = resize_event->size().width();
|
|
251
|
-
b = resize_event->size().height();
|
|
252
|
-
c = resize_event->oldSize().width();
|
|
253
|
-
d = resize_event->oldSize().height();
|
|
254
|
-
break;
|
|
255
|
-
}
|
|
256
|
-
default:
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
|
|
227
|
+
const QByteArray payload_json = QtRubyGeneratedEventPayloads::serialize_event_payload(et, event);
|
|
260
228
|
log_event_dispatch(watched, dispatch_target, et, event->isAccepted(), "dispatch");
|
|
261
|
-
event_callback_ref()(dispatch_target, et,
|
|
229
|
+
const int callback_result = event_callback_ref()(dispatch_target, et, payload_json.constData());
|
|
230
|
+
if (callback_result == kEventCallbackIgnore) {
|
|
231
|
+
event->ignore();
|
|
232
|
+
}
|
|
233
|
+
if (callback_result == kEventCallbackConsume) {
|
|
234
|
+
event->accept();
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
262
237
|
return QObject::eventFilter(watched, event);
|
|
263
238
|
}
|
|
264
239
|
};
|
data/lib/qt/event_runtime.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
module Qt
|
|
4
6
|
# Event/signal subscription runtime backed by generated native callbacks.
|
|
5
7
|
module EventRuntime
|
|
@@ -77,13 +79,7 @@ module Qt
|
|
|
77
79
|
|
|
78
80
|
def event_type_for(event_name)
|
|
79
81
|
key = event_name.to_sym
|
|
80
|
-
|
|
81
|
-
mouse_button_press: Qt::EventMouseButtonPress, mouse_button_release: Qt::EventMouseButtonRelease,
|
|
82
|
-
mouse_move: Qt::EventMouseMove, key_press: Qt::EventKeyPress, key_release: Qt::EventKeyRelease,
|
|
83
|
-
focus_in: Qt::EventFocusIn, focus_out: Qt::EventFocusOut, enter: Qt::EventEnter,
|
|
84
|
-
leave: Qt::EventLeave, resize: Qt::EventResize
|
|
85
|
-
}
|
|
86
|
-
event_type = map[key]
|
|
82
|
+
event_type = Qt::GENERATED_EVENT_TYPES[key]
|
|
87
83
|
raise ArgumentError, "unknown event: #{event_name.inspect}" unless event_type
|
|
88
84
|
|
|
89
85
|
event_type
|
|
@@ -93,10 +89,9 @@ module Qt
|
|
|
93
89
|
return if @event_callback
|
|
94
90
|
|
|
95
91
|
@event_callback = FFI::Function.new(
|
|
96
|
-
:
|
|
97
|
-
) do |object_handle, event_type,
|
|
98
|
-
|
|
99
|
-
payload = { type: event_type, a: a, b: b, c: c, d: d }
|
|
92
|
+
:int, %i[pointer int string]
|
|
93
|
+
) do |object_handle, event_type, payload_json|
|
|
94
|
+
payload = decode_event_payload(event_type, payload_json)
|
|
100
95
|
EventRuntimeDispatch.dispatch_event(@event_handlers, object_handle, event_type, payload)
|
|
101
96
|
end
|
|
102
97
|
|
|
@@ -120,6 +115,17 @@ module Qt
|
|
|
120
115
|
widget.respond_to?(:handle) ? widget.handle : widget
|
|
121
116
|
end
|
|
122
117
|
|
|
118
|
+
def decode_event_payload(event_type, payload_json)
|
|
119
|
+
payload =
|
|
120
|
+
if payload_json.nil? || payload_json.empty?
|
|
121
|
+
{}
|
|
122
|
+
else
|
|
123
|
+
JSON.parse(Qt::StringCodec.from_qt_text(payload_json), symbolize_names: true)
|
|
124
|
+
end
|
|
125
|
+
payload[:type] ||= event_type
|
|
126
|
+
payload
|
|
127
|
+
end
|
|
128
|
+
|
|
123
129
|
def ensure_native_bridge_ready!
|
|
124
130
|
Qt::Native.ensure_loaded!
|
|
125
131
|
Qt::Native.define_bridge_wrappers!
|
|
@@ -4,18 +4,26 @@
|
|
|
4
4
|
module Qt
|
|
5
5
|
# Dispatch helpers for event/signal callbacks from the native bridge.
|
|
6
6
|
module EventRuntimeDispatch
|
|
7
|
+
EVENT_RESULT_IGNORE = 0
|
|
8
|
+
EVENT_RESULT_CONTINUE = 1
|
|
9
|
+
EVENT_RESULT_CONSUME = 2
|
|
10
|
+
|
|
7
11
|
module_function
|
|
8
12
|
|
|
9
13
|
def dispatch_event(event_handlers, object_handle, event_type, payload)
|
|
10
|
-
return unless object_handle && event_handlers
|
|
14
|
+
return EVENT_RESULT_CONTINUE unless object_handle && event_handlers
|
|
11
15
|
|
|
12
16
|
per_widget = event_handlers[object_handle.address]
|
|
13
|
-
return unless per_widget
|
|
17
|
+
return EVENT_RESULT_CONTINUE unless per_widget
|
|
14
18
|
|
|
15
19
|
handlers = per_widget[event_type]
|
|
16
|
-
return unless handlers && !handlers.empty?
|
|
20
|
+
return EVENT_RESULT_CONTINUE unless handlers && !handlers.empty?
|
|
21
|
+
|
|
22
|
+
results = handlers.map { |handler| handler.call(payload) }
|
|
23
|
+
return EVENT_RESULT_CONSUME if results.any? { |result| result == true || result == :consume }
|
|
24
|
+
return EVENT_RESULT_IGNORE if results.any? { |result| result == false || result == :ignore }
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
EVENT_RESULT_CONTINUE
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def dispatch_signal(signal_handlers, object_handle, signal_index, payload)
|
data/lib/qt/version.rb
CHANGED
data/lib/qt.rb
CHANGED
|
@@ -8,8 +8,10 @@ GENERATED_DIR = File.join(ROOT, 'build', 'generated')
|
|
|
8
8
|
GENERATED_WIDGETS = File.join(GENERATED_DIR, 'widgets.rb')
|
|
9
9
|
GENERATED_API = File.join(GENERATED_DIR, 'bridge_api.rb')
|
|
10
10
|
GENERATED_CONSTANTS = File.join(GENERATED_DIR, 'constants.rb')
|
|
11
|
+
GENERATED_EVENT_PAYLOADS = File.join(GENERATED_DIR, 'event_payloads.rb')
|
|
11
12
|
|
|
12
|
-
unless File.exist?(GENERATED_WIDGETS) && File.exist?(GENERATED_API) && File.exist?(GENERATED_CONSTANTS)
|
|
13
|
+
unless File.exist?(GENERATED_WIDGETS) && File.exist?(GENERATED_API) && File.exist?(GENERATED_CONSTANTS) &&
|
|
14
|
+
File.exist?(GENERATED_EVENT_PAYLOADS)
|
|
13
15
|
ok = system(RbConfig.ruby, GENERATOR)
|
|
14
16
|
raise 'Failed to generate Qt Ruby bindings. Run: bundle exec rake compile' unless ok
|
|
15
17
|
end
|
|
@@ -30,6 +32,7 @@ require_relative 'qt/native'
|
|
|
30
32
|
require_relative 'qt/event_runtime_dispatch'
|
|
31
33
|
require_relative 'qt/event_runtime_qobject_methods'
|
|
32
34
|
require_relative 'qt/event_runtime'
|
|
35
|
+
require GENERATED_EVENT_PAYLOADS
|
|
33
36
|
require GENERATED_WIDGETS
|
|
34
37
|
require_relative 'qt/shortcut_compat'
|
|
35
38
|
Qt::GeneratedConstantsRuntime.apply_generated_scoped_constants!(Qt)
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
EVENT_PAYLOAD_COMPATIBILITY_ALIASES = {
|
|
4
|
+
'QMouseEvent' => { a: :x, b: :y, c: :button, d: :buttons },
|
|
5
|
+
'QKeyEvent' => { a: :key, b: :modifiers, c: :is_auto_repeat, d: :count },
|
|
6
|
+
'QResizeEvent' => { a: :width, b: :height, c: :old_width, d: :old_height },
|
|
7
|
+
'QWheelEvent' => { a: :pixel_delta_y, b: :angle_delta_y, c: :buttons, d: nil }
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
EVENT_PAYLOAD_EXCLUDED_METHODS = %w[
|
|
11
|
+
accepted clone ignore isAccepted registerEventType setAccepted spontaneous type
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
EVENT_PAYLOAD_SUPPORTED_COMPLEX_TYPES = %w[QPoint QPointF QSize].freeze
|
|
15
|
+
|
|
16
|
+
def event_payload_class_names(ast)
|
|
17
|
+
@event_payload_class_names ||= {}.compare_by_identity
|
|
18
|
+
return @event_payload_class_names[ast] if @event_payload_class_names.key?(ast)
|
|
19
|
+
|
|
20
|
+
index = ast_class_index(ast)
|
|
21
|
+
@event_payload_class_names[ast] = index[:methods_by_class].keys.select do |class_name|
|
|
22
|
+
class_name.start_with?('Q') && class_name.end_with?('Event') && class_inherits?(ast, class_name, 'QEvent')
|
|
23
|
+
end.sort
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def event_payload_ctor_type_param?(ctor_decl)
|
|
27
|
+
Array(ctor_decl['inner']).any? do |node|
|
|
28
|
+
next false unless node['kind'] == 'ParmVarDecl'
|
|
29
|
+
|
|
30
|
+
raw_type = node.dig('type', 'desugaredQualType') || node.dig('type', 'qualType')
|
|
31
|
+
name = node['name']
|
|
32
|
+
%w[type t].include?(name) && %w[Type QEvent::Type].include?(raw_type)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def event_payload_type_family_class_names(ast)
|
|
37
|
+
@event_payload_type_family_class_names ||= {}.compare_by_identity
|
|
38
|
+
return @event_payload_type_family_class_names[ast] if @event_payload_type_family_class_names.key?(ast)
|
|
39
|
+
|
|
40
|
+
@event_payload_type_family_class_names[ast] = event_payload_class_names(ast).select do |class_name|
|
|
41
|
+
collect_constructor_decls(ast, class_name).any? { |decl| event_payload_ctor_type_param?(decl) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def event_payload_class_stem(class_name)
|
|
46
|
+
class_name.sub(/\AQ/, '').sub(/Event\z/, '')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def event_payload_camel_tokens(name)
|
|
50
|
+
name.to_s.scan(/[A-Z]+(?=[A-Z][a-z]|\z)|[A-Z]?[a-z]+|\d+/)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def event_payload_token_sequence_present?(haystack, needle)
|
|
54
|
+
return false if haystack.empty? || needle.empty? || needle.length > haystack.length
|
|
55
|
+
|
|
56
|
+
0.upto(haystack.length - needle.length).any? do |start_idx|
|
|
57
|
+
haystack[start_idx, needle.length] == needle
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def event_payload_family_match?(event_name, class_name, mode)
|
|
62
|
+
event_tokens = event_payload_camel_tokens(event_name)
|
|
63
|
+
class_tokens = event_payload_camel_tokens(event_payload_class_stem(class_name))
|
|
64
|
+
return false if event_tokens.empty? || class_tokens.empty?
|
|
65
|
+
|
|
66
|
+
event_downcase = event_tokens.map(&:downcase)
|
|
67
|
+
class_downcase = class_tokens.map(&:downcase)
|
|
68
|
+
event_compact = event_downcase.join
|
|
69
|
+
class_compact = class_downcase.join
|
|
70
|
+
|
|
71
|
+
case mode
|
|
72
|
+
when :prefix
|
|
73
|
+
event_downcase.first(class_downcase.length) == class_downcase
|
|
74
|
+
when :suffix
|
|
75
|
+
event_downcase.last(class_downcase.length) == class_downcase
|
|
76
|
+
when :contiguous_tokens
|
|
77
|
+
event_payload_token_sequence_present?(event_downcase, class_downcase)
|
|
78
|
+
when :compact_substring
|
|
79
|
+
event_compact.include?(class_compact)
|
|
80
|
+
else
|
|
81
|
+
false
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def event_payload_most_specific_class_names(ast, class_names)
|
|
86
|
+
class_names.reject do |class_name|
|
|
87
|
+
class_names.any? do |other|
|
|
88
|
+
next false if other == class_name
|
|
89
|
+
|
|
90
|
+
class_inherits?(ast, other, class_name)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def event_payload_longest_stem_class_names(class_names)
|
|
96
|
+
stems = class_names.to_h do |class_name|
|
|
97
|
+
[class_name, event_payload_camel_tokens(event_payload_class_stem(class_name)).length]
|
|
98
|
+
end
|
|
99
|
+
longest = stems.values.max
|
|
100
|
+
class_names.select { |class_name| stems[class_name] == longest }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def resolve_event_payload_family_class_name(ast, event_name, warnings)
|
|
104
|
+
%i[prefix suffix contiguous_tokens compact_substring].each do |mode|
|
|
105
|
+
matches = event_payload_type_family_class_names(ast).select do |class_name|
|
|
106
|
+
event_payload_family_match?(event_name, class_name, mode)
|
|
107
|
+
end
|
|
108
|
+
next if matches.empty?
|
|
109
|
+
|
|
110
|
+
matches = event_payload_most_specific_class_names(ast, matches)
|
|
111
|
+
matches = event_payload_longest_stem_class_names(matches)
|
|
112
|
+
return matches.first if matches.length == 1
|
|
113
|
+
|
|
114
|
+
warnings << "Qt::EventPayload: ambiguous #{mode} family match for #{event_name}: #{matches.sort.join(', ')}"
|
|
115
|
+
return nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def resolve_event_payload_class_name(ast, event_name, warnings = [])
|
|
122
|
+
exact_class_name = "Q#{event_name}Event"
|
|
123
|
+
return exact_class_name if event_payload_class_names(ast).include?(exact_class_name)
|
|
124
|
+
|
|
125
|
+
resolve_event_payload_family_class_name(ast, event_name, warnings)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def supported_event_payload_return?(return_type, int_cast_types)
|
|
129
|
+
return true if event_payload_scalar_return?(return_type, int_cast_types)
|
|
130
|
+
return true if EVENT_PAYLOAD_SUPPORTED_COMPLEX_TYPES.include?(normalized_cpp_type_name(return_type))
|
|
131
|
+
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def event_payload_scalar_return?(return_type, int_cast_types)
|
|
136
|
+
raw = return_type.to_s.strip
|
|
137
|
+
return true if %w[bool int QString double float].include?(normalized_cpp_type_name(raw))
|
|
138
|
+
|
|
139
|
+
compact = raw.sub(/\Aconst\s+/, '').sub(/\s*&\z/, '').strip
|
|
140
|
+
compact.include?('::') && int_cast_types.include?(compact)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def event_payload_method_decl(ast, class_name, method_name, int_cast_types)
|
|
144
|
+
collect_method_decls_with_bases(ast, class_name, method_name).find do |decl|
|
|
145
|
+
next false unless decl['__effective_access'] == 'public'
|
|
146
|
+
next false if deprecated_method_decl?(decl)
|
|
147
|
+
|
|
148
|
+
parsed = parse_method_signature(decl)
|
|
149
|
+
next false unless parsed && parsed[:params].empty?
|
|
150
|
+
next false if EVENT_PAYLOAD_EXCLUDED_METHODS.include?(method_name)
|
|
151
|
+
|
|
152
|
+
supported_event_payload_return?(parsed[:return_type], int_cast_types)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def event_payload_method_candidates(ast, class_name, int_cast_types)
|
|
157
|
+
collect_method_names_with_bases(ast, class_name).sort.filter_map do |method_name|
|
|
158
|
+
decl = event_payload_method_decl(ast, class_name, method_name, int_cast_types)
|
|
159
|
+
next unless decl
|
|
160
|
+
|
|
161
|
+
parsed = parse_method_signature(decl)
|
|
162
|
+
{ method_name: method_name, return_type: parsed[:return_type] }
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def event_payload_base_name(method_name, return_type)
|
|
167
|
+
normalized = normalized_cpp_type_name(return_type)
|
|
168
|
+
snake = to_snake(method_name)
|
|
169
|
+
return '' if %w[QPoint QPointF].include?(normalized) && %w[position pos local_pos].include?(snake)
|
|
170
|
+
return 'global_' if %w[QPoint QPointF].include?(normalized) && %w[global_position global_pos].include?(snake)
|
|
171
|
+
return '' if normalized == 'QSize' && snake == 'size'
|
|
172
|
+
return 'old_' if normalized == 'QSize' && snake == 'old_size'
|
|
173
|
+
|
|
174
|
+
"#{snake}_"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def event_payload_fields_for_method(method_name, return_type, int_cast_types)
|
|
178
|
+
normalized = normalized_cpp_type_name(return_type)
|
|
179
|
+
base_name = event_payload_base_name(method_name, return_type)
|
|
180
|
+
getter = "typed_event->#{method_name}()"
|
|
181
|
+
|
|
182
|
+
case normalized
|
|
183
|
+
when 'bool'
|
|
184
|
+
[{ name: to_snake(method_name), cpp_expr: getter, json_type: :bool }]
|
|
185
|
+
when 'int'
|
|
186
|
+
[{ name: to_snake(method_name), cpp_expr: getter, json_type: :int }]
|
|
187
|
+
when 'QString'
|
|
188
|
+
[{ name: to_snake(method_name), cpp_expr: getter, json_type: :string }]
|
|
189
|
+
when 'double', 'float'
|
|
190
|
+
[{ name: to_snake(method_name), cpp_expr: getter, json_type: :float }]
|
|
191
|
+
when 'QPoint'
|
|
192
|
+
[
|
|
193
|
+
{ name: "#{base_name}x", cpp_expr: "#{getter}.x()", json_type: :int },
|
|
194
|
+
{ name: "#{base_name}y", cpp_expr: "#{getter}.y()", json_type: :int }
|
|
195
|
+
]
|
|
196
|
+
when 'QPointF'
|
|
197
|
+
[
|
|
198
|
+
{ name: "#{base_name}x", cpp_expr: "#{getter}.x()", json_type: :float },
|
|
199
|
+
{ name: "#{base_name}y", cpp_expr: "#{getter}.y()", json_type: :float }
|
|
200
|
+
]
|
|
201
|
+
when 'QSize'
|
|
202
|
+
[
|
|
203
|
+
{ name: "#{base_name}width", cpp_expr: "#{getter}.width()", json_type: :int },
|
|
204
|
+
{ name: "#{base_name}height", cpp_expr: "#{getter}.height()", json_type: :int }
|
|
205
|
+
]
|
|
206
|
+
else
|
|
207
|
+
compact = return_type.to_s.sub(/\Aconst\s+/, '').sub(/\s*&\z/, '').strip
|
|
208
|
+
return [] unless compact.include?('::') && int_cast_types.include?(compact)
|
|
209
|
+
|
|
210
|
+
[{ name: to_snake(method_name), cpp_expr: "static_cast<int>(#{getter})", json_type: :int }]
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def event_payload_compatibility_fields(class_name, field_names)
|
|
215
|
+
aliases = EVENT_PAYLOAD_COMPATIBILITY_ALIASES[class_name] || {}
|
|
216
|
+
aliases.each_with_object([]) do |(alias_name, target_name), out|
|
|
217
|
+
if target_name.nil?
|
|
218
|
+
out << { name: alias_name.to_s, source: nil, json_type: :int }
|
|
219
|
+
next
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
next unless field_names.include?(target_name.to_s)
|
|
223
|
+
|
|
224
|
+
out << { name: alias_name.to_s, source: target_name.to_s, json_type: :alias }
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def collect_event_payload_schema_for(ast, event_name, event_value, int_cast_types, warnings)
|
|
229
|
+
class_name = resolve_event_payload_class_name(ast, event_name, warnings)
|
|
230
|
+
return nil if class_name.nil?
|
|
231
|
+
return nil unless ast_class_index(ast)[:methods_by_class].key?(class_name)
|
|
232
|
+
|
|
233
|
+
field_specs = event_payload_method_candidates(ast, class_name, int_cast_types).flat_map do |entry|
|
|
234
|
+
event_payload_fields_for_method(entry[:method_name], entry[:return_type], int_cast_types)
|
|
235
|
+
end
|
|
236
|
+
field_specs.uniq! { |field| field[:name] }
|
|
237
|
+
|
|
238
|
+
compatibility_fields = event_payload_compatibility_fields(class_name, field_specs.map { |field| field[:name] })
|
|
239
|
+
schema = {
|
|
240
|
+
symbol_name: qevent_symbol_name(event_name),
|
|
241
|
+
event_name: event_name,
|
|
242
|
+
event_value: event_value,
|
|
243
|
+
constant_name: "Event#{event_name}",
|
|
244
|
+
class_name: class_name,
|
|
245
|
+
fields: field_specs,
|
|
246
|
+
compatibility_fields: compatibility_fields
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if field_specs.empty? && compatibility_fields.empty?
|
|
250
|
+
warnings << "Qt::EventPayload: #{event_name} resolved to #{class_name} but no payload fields were derived"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
schema
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def collect_event_payload_schemas(ast, warnings = [])
|
|
257
|
+
int_cast_types = ast_int_cast_type_set(ast)
|
|
258
|
+
collect_enum_constants_for_scope(ast, ['QEvent'], warnings).sort.filter_map do |event_name, event_value|
|
|
259
|
+
collect_event_payload_schema_for(ast, event_name, event_value, int_cast_types, warnings)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def generate_ruby_event_payloads(ast)
|
|
264
|
+
warnings = []
|
|
265
|
+
schemas = collect_event_payload_schemas(ast, warnings)
|
|
266
|
+
emit_generation_warnings(warnings)
|
|
267
|
+
|
|
268
|
+
lines = ['# frozen_string_literal: true', '', 'module Qt']
|
|
269
|
+
lines << ' GENERATED_EVENT_PAYLOAD_SCHEMAS = {'
|
|
270
|
+
schemas.each do |schema|
|
|
271
|
+
lines << " #{schema[:symbol_name].inspect} => {"
|
|
272
|
+
lines << " event_type: #{schema[:constant_name]},"
|
|
273
|
+
lines << " event_class: '#{schema[:class_name]}',"
|
|
274
|
+
lines << ' fields: ['
|
|
275
|
+
schema[:fields].each do |field|
|
|
276
|
+
lines << " { name: #{field[:name].inspect}, type: #{field[:json_type].inspect} },"
|
|
277
|
+
end
|
|
278
|
+
schema[:compatibility_fields].each do |field|
|
|
279
|
+
lines << " { name: #{field[:name].inspect}, type: #{field[:json_type].inspect} },"
|
|
280
|
+
end
|
|
281
|
+
lines << ' ]'
|
|
282
|
+
lines << ' },'
|
|
283
|
+
end
|
|
284
|
+
lines << ' }.freeze unless const_defined?(:GENERATED_EVENT_PAYLOAD_SCHEMAS, false)'
|
|
285
|
+
lines << 'end'
|
|
286
|
+
"#{lines.join("\n")}\n"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def cpp_json_insert_lines(field, source_expr = nil)
|
|
290
|
+
expr = source_expr || field[:cpp_expr]
|
|
291
|
+
case field[:json_type]
|
|
292
|
+
when :bool, :int, :float
|
|
293
|
+
["payload.insert(\"#{field[:name]}\", #{expr});"]
|
|
294
|
+
when :string
|
|
295
|
+
["payload.insert(\"#{field[:name]}\", #{expr});"]
|
|
296
|
+
else
|
|
297
|
+
[]
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def generate_cpp_event_payload_extractor(ast)
|
|
302
|
+
warnings = []
|
|
303
|
+
schemas = collect_event_payload_schemas(ast, warnings)
|
|
304
|
+
emit_generation_warnings(warnings)
|
|
305
|
+
|
|
306
|
+
includes = schemas.map { |schema| "#include <#{schema[:class_name]}>" }.uniq.sort
|
|
307
|
+
lines = []
|
|
308
|
+
lines.concat(includes)
|
|
309
|
+
lines << '#include <QEvent>'
|
|
310
|
+
lines << '#include <QJsonDocument>'
|
|
311
|
+
lines << '#include <QJsonObject>'
|
|
312
|
+
lines << ''
|
|
313
|
+
lines << 'namespace QtRubyGeneratedEventPayloads {'
|
|
314
|
+
lines << 'inline QByteArray serialize_event_payload(int event_type, QEvent* event) {'
|
|
315
|
+
lines << ' QJsonObject payload;'
|
|
316
|
+
lines << ' payload.insert("type", event_type);'
|
|
317
|
+
lines << ' switch (static_cast<QEvent::Type>(event_type)) {'
|
|
318
|
+
schemas.each do |schema|
|
|
319
|
+
lines << " case QEvent::#{schema[:event_name]}: {"
|
|
320
|
+
lines << " auto* typed_event = static_cast<#{schema[:class_name]}*>(event);"
|
|
321
|
+
schema[:fields].each do |field|
|
|
322
|
+
cpp_json_insert_lines(field).each { |line| lines << " #{line}" }
|
|
323
|
+
end
|
|
324
|
+
schema[:compatibility_fields].each do |field|
|
|
325
|
+
if field[:source]
|
|
326
|
+
lines << " payload.insert(\"#{field[:name]}\", payload.value(\"#{field[:source]}\"));"
|
|
327
|
+
else
|
|
328
|
+
lines << " payload.insert(\"#{field[:name]}\", 0);"
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
lines << ' break;'
|
|
332
|
+
lines << ' }'
|
|
333
|
+
end
|
|
334
|
+
lines << ' default:'
|
|
335
|
+
lines << ' break;'
|
|
336
|
+
lines << ' }'
|
|
337
|
+
lines << ' return QJsonDocument(payload).toJson(QJsonDocument::Compact);'
|
|
338
|
+
lines << '}'
|
|
339
|
+
lines << '} // namespace QtRubyGeneratedEventPayloads'
|
|
340
|
+
"#{lines.join("\n")}\n"
|
|
341
|
+
end
|
data/scripts/generate_bridge.rb
CHANGED
|
@@ -13,6 +13,8 @@ CPP_PATH = File.join(GENERATED_DIR, 'qt_ruby_bridge.cpp')
|
|
|
13
13
|
API_PATH = File.join(GENERATED_DIR, 'bridge_api.rb')
|
|
14
14
|
RUBY_WIDGETS_PATH = File.join(GENERATED_DIR, 'widgets.rb')
|
|
15
15
|
RUBY_CONSTANTS_PATH = File.join(GENERATED_DIR, 'constants.rb')
|
|
16
|
+
RUBY_EVENT_PAYLOADS_PATH = File.join(GENERATED_DIR, 'event_payloads.rb')
|
|
17
|
+
CPP_EVENT_PAYLOADS_PATH = File.join(GENERATED_DIR, 'event_payloads.inc')
|
|
16
18
|
|
|
17
19
|
# Universal generation policy: class set is discovered from AST per scope.
|
|
18
20
|
GENERATOR_SCOPE = (ENV['QT_RUBY_SCOPE'] || 'all').freeze
|
|
@@ -50,6 +52,7 @@ require_relative 'generate_bridge/auto_method_spec_resolver'
|
|
|
50
52
|
require_relative 'generate_bridge/cpp_method_return_emitter'
|
|
51
53
|
require_relative 'generate_bridge/ast_introspection'
|
|
52
54
|
require_relative 'generate_bridge/auto_methods'
|
|
55
|
+
require_relative 'generate_bridge/event_payloads'
|
|
53
56
|
require_relative 'generate_bridge/spec_discovery'
|
|
54
57
|
|
|
55
58
|
def next_trace_base(fetch_bases, cur, visited)
|
|
@@ -1017,6 +1020,30 @@ def collect_qt_namespace_enum_constants(ast, warnings = [])
|
|
|
1017
1020
|
constants
|
|
1018
1021
|
end
|
|
1019
1022
|
|
|
1023
|
+
def qevent_symbol_name(name)
|
|
1024
|
+
to_snake(name).to_sym
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
def collect_qevent_symbol_map(ast, warnings = [])
|
|
1028
|
+
symbol_map = {}
|
|
1029
|
+
|
|
1030
|
+
collect_enum_constants_for_scope(ast, ['QEvent'], warnings).each do |name, value|
|
|
1031
|
+
symbol_name = qevent_symbol_name(name)
|
|
1032
|
+
existing = symbol_map[symbol_name]
|
|
1033
|
+
if existing.nil?
|
|
1034
|
+
symbol_map[symbol_name] = { constant_name: "Event#{name}", value: value }
|
|
1035
|
+
next
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
next if existing[:value] == value && existing[:constant_name] == "Event#{name}"
|
|
1039
|
+
|
|
1040
|
+
warnings << "Qt::QEventSymbolMap: #{symbol_name}=Event#{name}(#{value}) conflicts with existing " \
|
|
1041
|
+
"#{existing[:constant_name]}(#{existing[:value]}); keeping existing #{existing[:constant_name]}"
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
symbol_map
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1020
1047
|
def collect_qt_scoped_enum_constants(ast, warnings = [])
|
|
1021
1048
|
constants_by_owner = Hash.new { |h, k| h[k] = {} }
|
|
1022
1049
|
|
|
@@ -1059,6 +1086,7 @@ def generate_ruby_constants(ast)
|
|
|
1059
1086
|
warnings = []
|
|
1060
1087
|
constants = collect_qt_namespace_enum_constants(ast, warnings)
|
|
1061
1088
|
scoped_constants = collect_qt_scoped_enum_constants(ast, warnings)
|
|
1089
|
+
event_symbol_map = collect_qevent_symbol_map(ast, warnings)
|
|
1062
1090
|
emit_generation_warnings(warnings)
|
|
1063
1091
|
lines = ['# frozen_string_literal: true', '', 'module Qt']
|
|
1064
1092
|
|
|
@@ -1076,6 +1104,12 @@ def generate_ruby_constants(ast)
|
|
|
1076
1104
|
lines << ' },'
|
|
1077
1105
|
end
|
|
1078
1106
|
lines << ' }.freeze unless const_defined?(:GENERATED_SCOPED_CONSTANTS, false)'
|
|
1107
|
+
lines << ''
|
|
1108
|
+
lines << ' GENERATED_EVENT_TYPES = {'
|
|
1109
|
+
event_symbol_map.sort.each do |symbol_name, entry|
|
|
1110
|
+
lines << " #{symbol_name.inspect} => #{entry[:constant_name]},"
|
|
1111
|
+
end
|
|
1112
|
+
lines << ' }.freeze unless const_defined?(:GENERATED_EVENT_TYPES, false)'
|
|
1079
1113
|
|
|
1080
1114
|
lines << 'end'
|
|
1081
1115
|
"#{lines.join("\n")}\n"
|
|
@@ -1104,6 +1138,14 @@ timed('write_ruby_constants') do
|
|
|
1104
1138
|
FileUtils.mkdir_p(File.dirname(RUBY_CONSTANTS_PATH))
|
|
1105
1139
|
File.write(RUBY_CONSTANTS_PATH, generate_ruby_constants(ast))
|
|
1106
1140
|
end
|
|
1141
|
+
timed('write_ruby_event_payloads') do
|
|
1142
|
+
FileUtils.mkdir_p(File.dirname(RUBY_EVENT_PAYLOADS_PATH))
|
|
1143
|
+
File.write(RUBY_EVENT_PAYLOADS_PATH, generate_ruby_event_payloads(ast))
|
|
1144
|
+
end
|
|
1145
|
+
timed('write_cpp_event_payloads') do
|
|
1146
|
+
FileUtils.mkdir_p(File.dirname(CPP_EVENT_PAYLOADS_PATH))
|
|
1147
|
+
File.write(CPP_EVENT_PAYLOADS_PATH, generate_cpp_event_payload_extractor(ast))
|
|
1148
|
+
end
|
|
1107
1149
|
timed('write_ruby_widgets') do
|
|
1108
1150
|
FileUtils.mkdir_p(File.dirname(RUBY_WIDGETS_PATH))
|
|
1109
1151
|
File.write(RUBY_WIDGETS_PATH, generate_ruby_widgets(effective_specs, super_qt_by_qt, wrapper_qt_classes))
|
|
@@ -1113,4 +1155,6 @@ debug_log("total=#{format('%.3fs', monotonic_now - total_start)}")
|
|
|
1113
1155
|
puts "Generated #{CPP_PATH}"
|
|
1114
1156
|
puts "Generated #{API_PATH}"
|
|
1115
1157
|
puts "Generated #{RUBY_CONSTANTS_PATH}"
|
|
1158
|
+
puts "Generated #{RUBY_EVENT_PAYLOADS_PATH}"
|
|
1159
|
+
puts "Generated #{CPP_EVENT_PAYLOADS_PATH}"
|
|
1116
1160
|
puts "Generated #{RUBY_WIDGETS_PATH}"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: qt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maksim Veynberg
|
|
@@ -72,6 +72,7 @@ files:
|
|
|
72
72
|
- scripts/generate_bridge/auto_methods.rb
|
|
73
73
|
- scripts/generate_bridge/core_utils.rb
|
|
74
74
|
- scripts/generate_bridge/cpp_method_return_emitter.rb
|
|
75
|
+
- scripts/generate_bridge/event_payloads.rb
|
|
75
76
|
- scripts/generate_bridge/ffi_api.rb
|
|
76
77
|
- scripts/generate_bridge/free_function_specs.rb
|
|
77
78
|
- scripts/generate_bridge/spec_discovery.rb
|
|
@@ -80,7 +81,7 @@ licenses:
|
|
|
80
81
|
- BSD-2-Clause
|
|
81
82
|
metadata:
|
|
82
83
|
source_code_uri: https://github.com/CyJimmy264/qt
|
|
83
|
-
changelog_uri: https://github.com/CyJimmy264/qt/
|
|
84
|
+
changelog_uri: https://github.com/CyJimmy264/qt/blob/master/CHANGELOG.md
|
|
84
85
|
bug_tracker_uri: https://github.com/CyJimmy264/qt/issues
|
|
85
86
|
rubygems_mfa_required: 'true'
|
|
86
87
|
rdoc_options: []
|