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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b476ba2b95707fc32d1995f8ae8dfc5c8068ab46020e00ea5442b1fb42dd3220
4
- data.tar.gz: 2ffb03be131be2c47d5d89f1094600d6e52fdb6891ffaabc40a3d8d7f6356c31
3
+ metadata.gz: be7294fb17e8d0cd4eeb66cc86fe6ee91940a95d5a6f3afaf9dfa44fb54445ba
4
+ data.tar.gz: 20683a100daf8ca9299a2227ae2605acbde2393e2444d020625c058cbc76993c
5
5
  SHA512:
6
- metadata.gz: 710c7df3d9c1950c14d35762a9223eb64cb4184573e0456fd864f5dd139f35224d03b144d0d121f465221e107e11a7c22e3053ee0b5508db72fe8e803ac3fc0c
7
- data.tar.gz: b8424801d5f61607f02ad9fb48cc5e6941f4dc811a38c4dbb5439d2e9f5dc561ed1ed814ec910d9ae81e49d84bf47fd40957ecfee07bb951900c80567ba49789
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 = void (*)(void*, int, int, int, int, int);
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
- int a = 0;
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, a, b, c, d);
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
- auto* mapper = new QSignalMapper(obj);
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();
@@ -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 prepare_signal_registration(handle, signal_name)
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
- @signal_handlers ||= {}
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 = Qt::Native.qobject_connect_signal(handle, signal_key)
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
- per_widget.delete(signal_key) if signal_key
61
- per_widget.clear unless signal_key
62
- Qt::Native.qobject_disconnect_signal(handle, signal_key)
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
- map = {
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
- :void, %i[pointer int int int int int]
97
- ) do |object_handle, event_type, *args|
98
- a, b, c, d = args
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(@signal_handlers, object_handle, signal_index, normalized_payload)
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
- handlers.each { |handler| handler.call(payload) }
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 dispatch_signal(signal_handlers, object_handle, signal_index, payload)
22
- return unless object_handle && signal_handlers
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
@@ -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[expected_qt_class] ||= begin
31
- base = fallback_wrapper_class(expected_qt_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
- return nil if expected_qt_class.nil? || !Qt.const_defined?(expected_qt_class, false)
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(expected_qt_class, false)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Qt
4
- VERSION = '0.1.4'
4
+ VERSION = '0.1.7'
5
5
  end
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