qt 0.1.4 → 0.1.7
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/ext/qt_ruby_bridge/runtime_signals.cpp +27 -1
- data/lib/qt/event_runtime.rb +81 -23
- data/lib/qt/event_runtime_dispatch.rb +22 -7
- data/lib/qt/generated_setter_aliases_runtime.rb +37 -0
- data/lib/qt/generated_singleton_forwarders_runtime.rb +24 -0
- data/lib/qt/object_list_codec.rb +23 -0
- data/lib/qt/object_wrapper.rb +101 -6
- data/lib/qt/version.rb +1 -1
- data/lib/qt.rb +10 -2
- data/scripts/generate_bridge/auto_methods.rb +1 -0
- data/scripts/generate_bridge/cpp_method_return_emitter.rb +12 -0
- data/scripts/generate_bridge/event_payloads.rb +341 -0
- data/scripts/generate_bridge.rb +114 -12
- metadata +6 -3
- data/lib/qt/children_tracking.rb +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be7294fb17e8d0cd4eeb66cc86fe6ee91940a95d5a6f3afaf9dfa44fb54445ba
|
|
4
|
+
data.tar.gz: 20683a100daf8ca9299a2227ae2605acbde2393e2444d020625c058cbc76993c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 151a6423964153f8e5f54cba0cc7c54d1ff6b4a7fbd10b003d2a19f6263831493d18ed6fbaeabf88d5791d3e3326e356ba50e0d5332e212c75341bf6ea3c2f50
|
|
7
|
+
data.tar.gz: 9e7542fc4bc481b7c3110f4b55aa4b33dd327babbe4aaad3dc4078ce2bd404a97a90022f1e49d97bd3ae05cb805e4db8fd0aef13e368974505dc361d29edfdd9
|
|
@@ -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
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "qt_ruby_runtime.hpp"
|
|
2
2
|
|
|
3
3
|
#include <QByteArray>
|
|
4
|
+
#include <QCoreApplication>
|
|
4
5
|
#include <QDateTimeEdit>
|
|
5
6
|
#include <QMetaMethod>
|
|
6
7
|
#include <QObject>
|
|
@@ -29,6 +30,11 @@ std::unordered_map<QObject*, SignalHandlersByIndex>& signal_handlers() {
|
|
|
29
30
|
return handlers;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
QObject* signal_callback_context() {
|
|
34
|
+
static QObject fallback_context;
|
|
35
|
+
return QCoreApplication::instance() ? static_cast<QObject*>(QCoreApplication::instance()) : &fallback_context;
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
int resolve_signal_index(QObject* obj, const char* signal_name) {
|
|
33
39
|
if (!obj || !signal_name) {
|
|
34
40
|
return -1;
|
|
@@ -122,7 +128,25 @@ int QtRubyRuntime::qobject_connect_signal(void* object_handle, const char* signa
|
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
const QMetaMethod signal_method = obj->metaObject()->method(signal_index);
|
|
125
|
-
|
|
131
|
+
if (signal_method.name() == "destroyed") {
|
|
132
|
+
QMetaObject::Connection direct_connection =
|
|
133
|
+
QObject::connect(obj, &QObject::destroyed, signal_callback_context(), [obj, signal_index](QObject*) {
|
|
134
|
+
if (!signal_callback_ref()) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
signal_callback_ref()(obj, signal_index, signal_payload_for(obj, signal_index));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!direct_connection) {
|
|
141
|
+
return -4;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
auto& by_index = signal_handlers()[obj];
|
|
145
|
+
by_index[signal_index].push_back(SignalHandler{signal_index, direct_connection, {}, nullptr});
|
|
146
|
+
return signal_index;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
auto* mapper = new QSignalMapper(QCoreApplication::instance());
|
|
126
150
|
mapper->setMapping(obj, signal_index);
|
|
127
151
|
|
|
128
152
|
const int map_slot_index = mapper->metaObject()->indexOfSlot("map()");
|
|
@@ -146,6 +170,8 @@ int QtRubyRuntime::qobject_connect_signal(void* object_handle, const char* signa
|
|
|
146
170
|
signal_callback_ref()(obj, mapped_signal_index, signal_payload_for(obj, mapped_signal_index));
|
|
147
171
|
});
|
|
148
172
|
|
|
173
|
+
QObject::connect(obj, &QObject::destroyed, mapper, [mapper]() { mapper->deleteLater(); });
|
|
174
|
+
|
|
149
175
|
if (!mapped_connection) {
|
|
150
176
|
QObject::disconnect(signal_connection);
|
|
151
177
|
mapper->deleteLater();
|
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
|
|
@@ -28,22 +30,30 @@ module Qt
|
|
|
28
30
|
ensure_signal_callback!
|
|
29
31
|
|
|
30
32
|
handle = widget_handle(widget) || raise(ArgumentError, 'widget handle is required')
|
|
31
|
-
per_signal = prepare_signal_registration(handle, signal_name)
|
|
33
|
+
per_signal = prepare_signal_registration(@signal_handlers ||= {}, handle, signal_name)
|
|
32
34
|
per_signal[:blocks] << block
|
|
33
35
|
true
|
|
34
36
|
end
|
|
35
37
|
|
|
36
|
-
def
|
|
38
|
+
def on_internal_signal(widget, signal_name, &block)
|
|
39
|
+
raise ArgumentError, 'pass block to on_internal_signal' unless block
|
|
40
|
+
|
|
41
|
+
ensure_native_bridge_ready!
|
|
42
|
+
ensure_signal_callback!
|
|
43
|
+
|
|
44
|
+
handle = widget_handle(widget) || raise(ArgumentError, 'widget handle is required')
|
|
45
|
+
per_signal = prepare_signal_registration(@internal_signal_handlers ||= {}, handle, signal_name)
|
|
46
|
+
per_signal[:blocks] << block
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def prepare_signal_registration(signal_store, handle, signal_name)
|
|
37
51
|
signal_key = signal_name.to_s
|
|
38
52
|
raise ArgumentError, 'signal name is required' if signal_key.empty?
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
per_signal = ((@signal_handlers[handle.address] ||= {})[signal_key] ||= { index: nil, blocks: [] })
|
|
54
|
+
per_signal = ((signal_store[handle.address] ||= {})[signal_key] ||= { index: nil, blocks: [] })
|
|
42
55
|
if per_signal[:index].nil?
|
|
43
|
-
index =
|
|
44
|
-
raise ArgumentError, "failed to connect signal #{signal_key.inspect} (code=#{index})" if index.negative?
|
|
45
|
-
|
|
46
|
-
per_signal[:index] = index
|
|
56
|
+
per_signal[:index] = acquire_signal_registration(handle, signal_key)
|
|
47
57
|
end
|
|
48
58
|
per_signal
|
|
49
59
|
end
|
|
@@ -57,9 +67,14 @@ module Qt
|
|
|
57
67
|
return false if per_widget.nil?
|
|
58
68
|
|
|
59
69
|
signal_key = signal_name&.to_s
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
if signal_key
|
|
71
|
+
per_widget.delete(signal_key)
|
|
72
|
+
release_signal_registration(handle, signal_key)
|
|
73
|
+
else
|
|
74
|
+
per_widget.keys.each { |registered_signal| release_signal_registration(handle, registered_signal) }
|
|
75
|
+
per_widget.clear
|
|
76
|
+
end
|
|
77
|
+
@signal_handlers.delete(handle.address) if per_widget.empty?
|
|
63
78
|
true
|
|
64
79
|
end
|
|
65
80
|
|
|
@@ -77,13 +92,7 @@ module Qt
|
|
|
77
92
|
|
|
78
93
|
def event_type_for(event_name)
|
|
79
94
|
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]
|
|
95
|
+
event_type = Qt::GENERATED_EVENT_TYPES[key]
|
|
87
96
|
raise ArgumentError, "unknown event: #{event_name.inspect}" unless event_type
|
|
88
97
|
|
|
89
98
|
event_type
|
|
@@ -93,10 +102,9 @@ module Qt
|
|
|
93
102
|
return if @event_callback
|
|
94
103
|
|
|
95
104
|
@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 }
|
|
105
|
+
:int, %i[pointer int string]
|
|
106
|
+
) do |object_handle, event_type, payload_json|
|
|
107
|
+
payload = decode_event_payload(event_type, payload_json)
|
|
100
108
|
EventRuntimeDispatch.dispatch_event(@event_handlers, object_handle, event_type, payload)
|
|
101
109
|
end
|
|
102
110
|
|
|
@@ -108,7 +116,9 @@ module Qt
|
|
|
108
116
|
|
|
109
117
|
@signal_callback = FFI::Function.new(:void, %i[pointer int string]) do |object_handle, signal_index, payload|
|
|
110
118
|
normalized_payload = payload.nil? ? nil : Qt::StringCodec.from_qt_text(payload)
|
|
111
|
-
EventRuntimeDispatch.dispatch_signal(
|
|
119
|
+
EventRuntimeDispatch.dispatch_signal(
|
|
120
|
+
@internal_signal_handlers, @signal_handlers, object_handle, signal_index, normalized_payload
|
|
121
|
+
)
|
|
112
122
|
end
|
|
113
123
|
|
|
114
124
|
Qt::Native.set_signal_callback(@signal_callback)
|
|
@@ -120,6 +130,17 @@ module Qt
|
|
|
120
130
|
widget.respond_to?(:handle) ? widget.handle : widget
|
|
121
131
|
end
|
|
122
132
|
|
|
133
|
+
def decode_event_payload(event_type, payload_json)
|
|
134
|
+
payload =
|
|
135
|
+
if payload_json.nil? || payload_json.empty?
|
|
136
|
+
{}
|
|
137
|
+
else
|
|
138
|
+
JSON.parse(Qt::StringCodec.from_qt_text(payload_json), symbolize_names: true)
|
|
139
|
+
end
|
|
140
|
+
payload[:type] ||= event_type
|
|
141
|
+
payload
|
|
142
|
+
end
|
|
143
|
+
|
|
123
144
|
def ensure_native_bridge_ready!
|
|
124
145
|
Qt::Native.ensure_loaded!
|
|
125
146
|
Qt::Native.define_bridge_wrappers!
|
|
@@ -135,5 +156,42 @@ module Qt
|
|
|
135
156
|
@event_handlers.delete(handle.address)
|
|
136
157
|
end
|
|
137
158
|
end
|
|
159
|
+
|
|
160
|
+
def acquire_signal_registration(handle, signal_key)
|
|
161
|
+
@signal_registrations ||= {}
|
|
162
|
+
per_widget = (@signal_registrations[handle.address] ||= {})
|
|
163
|
+
registration = (per_widget[signal_key] ||= { index: nil, refcount: 0 })
|
|
164
|
+
if registration[:index].nil?
|
|
165
|
+
index = Qt::Native.qobject_connect_signal(handle, signal_key)
|
|
166
|
+
raise ArgumentError, "failed to connect signal #{signal_key.inspect} (code=#{index})" if index.negative?
|
|
167
|
+
|
|
168
|
+
registration[:index] = index
|
|
169
|
+
end
|
|
170
|
+
registration[:refcount] += 1
|
|
171
|
+
registration[:index]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def release_signal_registration(handle, signal_key)
|
|
175
|
+
return if @signal_registrations.nil?
|
|
176
|
+
|
|
177
|
+
per_widget = @signal_registrations[handle.address]
|
|
178
|
+
return if per_widget.nil?
|
|
179
|
+
|
|
180
|
+
registration = per_widget[signal_key]
|
|
181
|
+
return if registration.nil?
|
|
182
|
+
|
|
183
|
+
registration[:refcount] -= 1
|
|
184
|
+
return if registration[:refcount].positive?
|
|
185
|
+
|
|
186
|
+
Qt::Native.qobject_disconnect_signal(handle, signal_key)
|
|
187
|
+
per_widget.delete(signal_key)
|
|
188
|
+
@signal_registrations.delete(handle.address) if per_widget.empty?
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def clear_signal_registrations_for_address(address)
|
|
192
|
+
@signal_handlers&.delete(address)
|
|
193
|
+
@internal_signal_handlers&.delete(address)
|
|
194
|
+
@signal_registrations&.delete(address)
|
|
195
|
+
end
|
|
138
196
|
end
|
|
139
197
|
end
|
|
@@ -4,22 +4,37 @@
|
|
|
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 }
|
|
25
|
+
|
|
26
|
+
EVENT_RESULT_CONTINUE
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def dispatch_signal(internal_signal_handlers, signal_handlers, object_handle, signal_index, payload)
|
|
30
|
+
return unless object_handle
|
|
17
31
|
|
|
18
|
-
|
|
32
|
+
dispatch_signal_store(signal_handlers, object_handle, signal_index, payload)
|
|
33
|
+
dispatch_signal_store(internal_signal_handlers, object_handle, signal_index, payload)
|
|
19
34
|
end
|
|
20
35
|
|
|
21
|
-
def
|
|
22
|
-
return unless
|
|
36
|
+
def dispatch_signal_store(signal_handlers, object_handle, signal_index, payload)
|
|
37
|
+
return unless signal_handlers
|
|
23
38
|
|
|
24
39
|
per_widget = signal_handlers[object_handle.address]
|
|
25
40
|
return unless per_widget
|
|
@@ -28,7 +43,7 @@ module Qt
|
|
|
28
43
|
next unless entry[:index] == signal_index
|
|
29
44
|
|
|
30
45
|
typed_payload = Qt::DateTimeCodec.decode_for_signal(signal_name, payload)
|
|
31
|
-
entry[:blocks].each { |handler| handler.call(typed_payload) }
|
|
46
|
+
entry[:blocks].dup.each { |handler| handler.call(typed_payload) }
|
|
32
47
|
end
|
|
33
48
|
end
|
|
34
49
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
module GeneratedSetterAliasesRuntime
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def apply!(qt_module)
|
|
8
|
+
qt_module.constants(false).each do |const_name|
|
|
9
|
+
klass = qt_module.const_get(const_name, false)
|
|
10
|
+
next unless klass.is_a?(Class)
|
|
11
|
+
|
|
12
|
+
apply_instance_aliases!(klass)
|
|
13
|
+
apply_singleton_aliases!(klass)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def apply_instance_aliases!(klass)
|
|
18
|
+
apply_aliases_to_target!(klass, klass, :QT_API_SETTER_ALIASES)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def apply_singleton_aliases!(klass)
|
|
22
|
+
apply_aliases_to_target!(klass.singleton_class, klass, :QT_API_SINGLETON_SETTER_ALIASES)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def apply_aliases_to_target!(target, owner, constant_name)
|
|
26
|
+
return unless owner.const_defined?(constant_name, false)
|
|
27
|
+
|
|
28
|
+
owner.const_get(constant_name, false).each do |alias_name, setter_name|
|
|
29
|
+
next if target.method_defined?(alias_name) || target.private_method_defined?(alias_name)
|
|
30
|
+
|
|
31
|
+
target.send(:define_method, alias_name) do |value|
|
|
32
|
+
public_send(setter_name, value)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
module GeneratedSingletonForwardersRuntime
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def apply!(qt_module)
|
|
8
|
+
qt_module.constants(false).each do |const_name|
|
|
9
|
+
klass = qt_module.const_get(const_name, false)
|
|
10
|
+
next unless klass.is_a?(Class)
|
|
11
|
+
next unless klass.const_defined?(:QT_API_SINGLETON_FORWARDERS, false)
|
|
12
|
+
|
|
13
|
+
klass.const_get(:QT_API_SINGLETON_FORWARDERS, false).each do |method_name|
|
|
14
|
+
next if klass.instance_methods(false).include?(method_name.to_sym)
|
|
15
|
+
next if klass.private_instance_methods(false).include?(method_name.to_sym)
|
|
16
|
+
|
|
17
|
+
klass.send(:define_method, method_name) do |*args, &block|
|
|
18
|
+
self.class.public_send(method_name, *args, &block)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Qt
|
|
6
|
+
# Decodes QObjectList bridge payloads into canonical Ruby wrappers.
|
|
7
|
+
module ObjectListCodec
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def decode(payload, expected_qt_class = 'QObject')
|
|
11
|
+
raw = Qt::StringCodec.from_qt_text(payload.to_s)
|
|
12
|
+
return [] if raw.empty?
|
|
13
|
+
|
|
14
|
+
JSON.parse(raw).filter_map do |address|
|
|
15
|
+
next if address.nil? || address.to_s.empty?
|
|
16
|
+
|
|
17
|
+
Qt::ObjectWrapper.wrap(FFI::Pointer.new(Integer(address, 10)), expected_qt_class)
|
|
18
|
+
end
|
|
19
|
+
rescue JSON::ParserError, ArgumentError
|
|
20
|
+
[]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/qt/object_wrapper.rb
CHANGED
|
@@ -5,14 +5,24 @@ module Qt
|
|
|
5
5
|
module ObjectWrapper
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
|
+
module ConstructorCacheHook
|
|
9
|
+
def initialize(*args, &block)
|
|
10
|
+
super
|
|
11
|
+
Qt::ObjectWrapper.register_wrapper(self)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
8
15
|
def wrap(pointer, expected_qt_class = nil)
|
|
9
16
|
return nil if null_pointer?(pointer)
|
|
10
17
|
return pointer if pointer.respond_to?(:handle)
|
|
11
18
|
|
|
19
|
+
cached = cached_wrapper_for(pointer)
|
|
20
|
+
return cached if cached
|
|
21
|
+
|
|
12
22
|
klass = resolve_wrapper_class(pointer, expected_qt_class) || fallback_wrapper_class(expected_qt_class)
|
|
13
23
|
return pointer unless klass
|
|
14
24
|
|
|
15
|
-
instantiate_wrapper(klass, pointer)
|
|
25
|
+
register_wrapper(instantiate_wrapper(klass, pointer))
|
|
16
26
|
end
|
|
17
27
|
|
|
18
28
|
def null_pointer?(pointer)
|
|
@@ -26,9 +36,10 @@ module Qt
|
|
|
26
36
|
end
|
|
27
37
|
|
|
28
38
|
def candidate_wrapper_classes(expected_qt_class)
|
|
39
|
+
normalized_qt_class = normalize_expected_qt_class(expected_qt_class)
|
|
29
40
|
@candidate_wrapper_classes ||= {}
|
|
30
|
-
@candidate_wrapper_classes[
|
|
31
|
-
base = fallback_wrapper_class(
|
|
41
|
+
@candidate_wrapper_classes[normalized_qt_class] ||= begin
|
|
42
|
+
base = fallback_wrapper_class(normalized_qt_class)
|
|
32
43
|
wrappers = qobject_wrapper_classes
|
|
33
44
|
wrappers = wrappers.select { |klass| klass <= base } if base
|
|
34
45
|
wrappers.sort_by { |klass| -inheritance_depth(klass) }
|
|
@@ -47,23 +58,95 @@ module Qt
|
|
|
47
58
|
end
|
|
48
59
|
|
|
49
60
|
def fallback_wrapper_class(expected_qt_class)
|
|
50
|
-
|
|
61
|
+
normalized_qt_class = normalize_expected_qt_class(expected_qt_class)
|
|
62
|
+
return nil unless normalized_qt_class
|
|
63
|
+
return nil unless Qt.const_defined?(normalized_qt_class, false)
|
|
51
64
|
|
|
52
|
-
klass = Qt.const_get(
|
|
65
|
+
klass = Qt.const_get(normalized_qt_class, false)
|
|
53
66
|
return nil unless klass.is_a?(Class)
|
|
54
67
|
return nil unless klass.const_defined?(:QT_CLASS, false)
|
|
55
68
|
return nil unless klass <= Qt::QObject
|
|
56
69
|
|
|
57
70
|
klass
|
|
71
|
+
rescue NameError
|
|
72
|
+
nil
|
|
58
73
|
end
|
|
59
74
|
|
|
60
75
|
def instantiate_wrapper(klass, pointer)
|
|
61
76
|
wrapped = klass.allocate
|
|
62
77
|
wrapped.instance_variable_set(:@handle, pointer)
|
|
63
|
-
wrapped.init_children_tracking! if wrapped.respond_to?(:init_children_tracking!, true)
|
|
64
78
|
wrapped
|
|
65
79
|
end
|
|
66
80
|
|
|
81
|
+
def cached_wrapper_for(pointer)
|
|
82
|
+
wrapper_cache[pointer.address]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def register_wrapper(wrapper)
|
|
86
|
+
return wrapper unless wrapper.respond_to?(:handle)
|
|
87
|
+
|
|
88
|
+
pointer = wrapper.handle
|
|
89
|
+
return wrapper if null_pointer?(pointer)
|
|
90
|
+
|
|
91
|
+
cached = cached_wrapper_for(pointer)
|
|
92
|
+
return cached if cached
|
|
93
|
+
|
|
94
|
+
cache_wrapper(wrapper)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def cache_wrapper(wrapper)
|
|
98
|
+
pointer = wrapper.handle
|
|
99
|
+
wrapper_cache[pointer.address] = wrapper
|
|
100
|
+
ensure_destroy_hook(wrapper, pointer)
|
|
101
|
+
wrapper
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def invalidate_cached_wrapper(pointer_or_address, expected_wrapper = nil)
|
|
105
|
+
address = pointer_or_address.is_a?(Integer) ? pointer_or_address : pointer_or_address.address
|
|
106
|
+
cached = wrapper_cache[address]
|
|
107
|
+
return unless cached
|
|
108
|
+
return if expected_wrapper && !cached.equal?(expected_wrapper)
|
|
109
|
+
|
|
110
|
+
wrapper_cache.delete(address)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def reset_cache!
|
|
114
|
+
@wrapper_cache = {}
|
|
115
|
+
@destroy_hook_addresses = {}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def install_constructor_cache_hooks!
|
|
119
|
+
qobject_wrapper_classes.each do |klass|
|
|
120
|
+
next if klass.instance_variable_defined?(:@__qt_object_wrapper_constructor_hook_installed)
|
|
121
|
+
|
|
122
|
+
klass.prepend(ConstructorCacheHook)
|
|
123
|
+
klass.instance_variable_set(:@__qt_object_wrapper_constructor_hook_installed, true)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def wrapper_cache
|
|
128
|
+
@wrapper_cache ||= {}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def destroy_hook_addresses
|
|
132
|
+
@destroy_hook_addresses ||= {}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def ensure_destroy_hook(wrapper, pointer)
|
|
136
|
+
address = pointer.address
|
|
137
|
+
return if destroy_hook_addresses[address]
|
|
138
|
+
|
|
139
|
+
destroy_hook_addresses[address] = true
|
|
140
|
+
Qt::EventRuntime.on_internal_signal(pointer, 'destroyed()') do |_payload|
|
|
141
|
+
destroy_hook_addresses.delete(address)
|
|
142
|
+
Qt::EventRuntime.clear_signal_registrations_for_address(address)
|
|
143
|
+
invalidate_cached_wrapper(address, wrapper)
|
|
144
|
+
end
|
|
145
|
+
rescue StandardError
|
|
146
|
+
destroy_hook_addresses.delete(address)
|
|
147
|
+
raise
|
|
148
|
+
end
|
|
149
|
+
|
|
67
150
|
def inheritance_depth(klass)
|
|
68
151
|
depth = 0
|
|
69
152
|
current = klass
|
|
@@ -73,5 +156,17 @@ module Qt
|
|
|
73
156
|
end
|
|
74
157
|
depth
|
|
75
158
|
end
|
|
159
|
+
|
|
160
|
+
def normalize_expected_qt_class(expected_qt_class)
|
|
161
|
+
return nil if expected_qt_class.nil?
|
|
162
|
+
|
|
163
|
+
value = expected_qt_class.to_s.strip
|
|
164
|
+
return nil if value.empty?
|
|
165
|
+
|
|
166
|
+
value = value.delete_prefix('Qt::')
|
|
167
|
+
return nil unless value.match?(/\A[A-Z]\w*\z/)
|
|
168
|
+
|
|
169
|
+
value
|
|
170
|
+
end
|
|
76
171
|
end
|
|
77
172
|
end
|
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
|
|
@@ -20,17 +22,23 @@ require_relative 'qt/constants'
|
|
|
20
22
|
require_relative 'qt/string_codec'
|
|
21
23
|
require_relative 'qt/date_time_codec'
|
|
22
24
|
require_relative 'qt/key_sequence_codec'
|
|
25
|
+
require_relative 'qt/object_list_codec'
|
|
23
26
|
require_relative 'qt/variant_codec'
|
|
24
27
|
require_relative 'qt/inspectable'
|
|
25
|
-
require_relative 'qt/children_tracking'
|
|
26
28
|
require_relative 'qt/object_wrapper'
|
|
27
29
|
require_relative 'qt/application_lifecycle'
|
|
28
30
|
require_relative 'qt/bridge'
|
|
29
31
|
require_relative 'qt/native'
|
|
32
|
+
require_relative 'qt/generated_setter_aliases_runtime'
|
|
33
|
+
require_relative 'qt/generated_singleton_forwarders_runtime'
|
|
30
34
|
require_relative 'qt/event_runtime_dispatch'
|
|
31
35
|
require_relative 'qt/event_runtime_qobject_methods'
|
|
32
36
|
require_relative 'qt/event_runtime'
|
|
37
|
+
require GENERATED_EVENT_PAYLOADS
|
|
33
38
|
require GENERATED_WIDGETS
|
|
39
|
+
Qt::GeneratedSetterAliasesRuntime.apply!(Qt)
|
|
40
|
+
Qt::GeneratedSingletonForwardersRuntime.apply!(Qt)
|
|
41
|
+
Qt::ObjectWrapper.install_constructor_cache_hooks!
|
|
34
42
|
require_relative 'qt/shortcut_compat'
|
|
35
43
|
Qt::GeneratedConstantsRuntime.apply_generated_scoped_constants!(Qt)
|
|
36
44
|
|