qt 0.1.0
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 +7 -0
- data/LICENSE +27 -0
- data/README.md +303 -0
- data/Rakefile +94 -0
- data/examples/development_ordered_demos/01_dsl_hello.rb +22 -0
- data/examples/development_ordered_demos/02_live_layout_console.rb +137 -0
- data/examples/development_ordered_demos/03_component_showcase.rb +235 -0
- data/examples/development_ordered_demos/04_paint_simple.rb +147 -0
- data/examples/development_ordered_demos/05_tetris_simple.rb +295 -0
- data/examples/development_ordered_demos/06_timetrap_clockify.rb +759 -0
- data/examples/development_ordered_demos/07_peek_like_recorder.rb +597 -0
- data/examples/qtproject/widgets/itemviews/spreadsheet/main.rb +252 -0
- data/examples/qtproject/widgets/widgetsgallery/main.rb +184 -0
- data/ext/qt_ruby_bridge/extconf.rb +75 -0
- data/ext/qt_ruby_bridge/qt_ruby_runtime.hpp +23 -0
- data/ext/qt_ruby_bridge/runtime_events.cpp +408 -0
- data/ext/qt_ruby_bridge/runtime_signals.cpp +212 -0
- data/lib/qt/application_lifecycle.rb +44 -0
- data/lib/qt/bridge.rb +95 -0
- data/lib/qt/children_tracking.rb +15 -0
- data/lib/qt/constants.rb +10 -0
- data/lib/qt/date_time_codec.rb +104 -0
- data/lib/qt/errors.rb +6 -0
- data/lib/qt/event_runtime.rb +139 -0
- data/lib/qt/event_runtime_dispatch.rb +35 -0
- data/lib/qt/event_runtime_qobject_methods.rb +41 -0
- data/lib/qt/generated_constants_runtime.rb +33 -0
- data/lib/qt/inspectable.rb +29 -0
- data/lib/qt/key_sequence_codec.rb +22 -0
- data/lib/qt/native.rb +93 -0
- data/lib/qt/shortcut_compat.rb +30 -0
- data/lib/qt/string_codec.rb +44 -0
- data/lib/qt/variant_codec.rb +78 -0
- data/lib/qt/version.rb +5 -0
- data/lib/qt.rb +47 -0
- data/scripts/generate_bridge/ast_introspection.rb +267 -0
- data/scripts/generate_bridge/auto_method_spec_resolver.rb +37 -0
- data/scripts/generate_bridge/auto_methods.rb +438 -0
- data/scripts/generate_bridge/core_utils.rb +114 -0
- data/scripts/generate_bridge/cpp_method_return_emitter.rb +93 -0
- data/scripts/generate_bridge/ffi_api.rb +46 -0
- data/scripts/generate_bridge/free_function_specs.rb +289 -0
- data/scripts/generate_bridge/spec_discovery.rb +313 -0
- data/scripts/generate_bridge.rb +1113 -0
- metadata +99 -0
data/lib/qt/bridge.rb
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
require 'rbconfig'
|
|
5
|
+
|
|
6
|
+
module Qt
|
|
7
|
+
# Bridge helpers for registering and disposing wrapped native objects.
|
|
8
|
+
module Bridge
|
|
9
|
+
extend FFI::Library
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def load!
|
|
14
|
+
return true if @loaded
|
|
15
|
+
|
|
16
|
+
ffi_lib(library_candidates)
|
|
17
|
+
attach_api
|
|
18
|
+
@loaded = true
|
|
19
|
+
rescue LoadError, FFI::NotFoundError => e
|
|
20
|
+
@loaded = false
|
|
21
|
+
@load_error = e
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def loaded?
|
|
26
|
+
!!@loaded
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def load_error
|
|
30
|
+
@load_error
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def library_candidates
|
|
34
|
+
@library_candidates ||= begin
|
|
35
|
+
ext = RbConfig::CONFIG['DLEXT']
|
|
36
|
+
root = File.expand_path('../..', __dir__)
|
|
37
|
+
load_path_candidates = load_path_library_candidates(ext)
|
|
38
|
+
gem_extension_candidates = gem_extension_library_candidates(ext)
|
|
39
|
+
|
|
40
|
+
[
|
|
41
|
+
File.join(root, 'build', 'qt', "qt_ruby_bridge.#{ext}"),
|
|
42
|
+
File.join(root, 'lib', 'qt', "qt_ruby_bridge.#{ext}"),
|
|
43
|
+
*gem_extension_candidates,
|
|
44
|
+
*load_path_candidates,
|
|
45
|
+
'qt_ruby_bridge'
|
|
46
|
+
].uniq
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def load_path_library_candidates(ext)
|
|
51
|
+
$LOAD_PATH.flat_map do |entry|
|
|
52
|
+
[
|
|
53
|
+
File.join(entry, 'qt', "qt_ruby_bridge.#{ext}"),
|
|
54
|
+
File.join(entry, "qt_ruby_bridge.#{ext}")
|
|
55
|
+
]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def gem_extension_library_candidates(ext)
|
|
60
|
+
spec = Gem::Specification.find_by_name('qt')
|
|
61
|
+
extension_dir = spec&.extension_dir
|
|
62
|
+
return [] if extension_dir.nil? || extension_dir.empty?
|
|
63
|
+
|
|
64
|
+
[
|
|
65
|
+
File.join(extension_dir, 'qt', "qt_ruby_bridge.#{ext}"),
|
|
66
|
+
File.join(extension_dir, "qt_ruby_bridge.#{ext}")
|
|
67
|
+
]
|
|
68
|
+
rescue Gem::LoadError
|
|
69
|
+
[]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def attach_api
|
|
73
|
+
return if @api_attached
|
|
74
|
+
|
|
75
|
+
ensure_generated_api!
|
|
76
|
+
Qt::BridgeAPI::FUNCTIONS.each do |fn|
|
|
77
|
+
attach_function fn[:name], fn[:args], fn[:return]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@api_attached = true
|
|
81
|
+
end
|
|
82
|
+
private_class_method :attach_api
|
|
83
|
+
|
|
84
|
+
def ensure_generated_api!
|
|
85
|
+
return if defined?(Qt::BridgeAPI::FUNCTIONS)
|
|
86
|
+
|
|
87
|
+
root = File.expand_path('../..', __dir__)
|
|
88
|
+
generated_api = File.join(root, 'build', 'generated', 'bridge_api.rb')
|
|
89
|
+
generator = File.join(root, 'scripts', 'generate_bridge.rb')
|
|
90
|
+
system(RbConfig.ruby, generator) unless File.exist?(generated_api)
|
|
91
|
+
require generated_api
|
|
92
|
+
end
|
|
93
|
+
private_class_method :ensure_generated_api!
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
# Child object tracking to mirror Qt parent/child ownership in Ruby.
|
|
5
|
+
module ChildrenTracking
|
|
6
|
+
def init_children_tracking!
|
|
7
|
+
@children = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_child(child)
|
|
11
|
+
@children ||= []
|
|
12
|
+
@children << child
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/qt/constants.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'generated_constants_runtime'
|
|
4
|
+
|
|
5
|
+
module Qt
|
|
6
|
+
GENERATED_CONSTANTS = File.expand_path('../../build/generated/constants.rb', __dir__)
|
|
7
|
+
require GENERATED_CONSTANTS if File.exist?(GENERATED_CONSTANTS)
|
|
8
|
+
|
|
9
|
+
GeneratedConstantsRuntime.apply_key_aliases!(self)
|
|
10
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
module Qt
|
|
7
|
+
# Codec for typed QDateTime/QDate/QTime bridge payloads.
|
|
8
|
+
module DateTimeCodec
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
DATETIME_PREFIX = 'qtdt:'
|
|
12
|
+
DATE_PREFIX = 'qtdate:'
|
|
13
|
+
TIME_PREFIX = 'qttime:'
|
|
14
|
+
|
|
15
|
+
def encode_qdatetime(value)
|
|
16
|
+
time = coerce_to_time(value)
|
|
17
|
+
"#{DATETIME_PREFIX}#{time.iso8601(6)}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def decode_qdatetime(value)
|
|
21
|
+
raw = StringCodec.from_qt_text(value.to_s)
|
|
22
|
+
payload = raw.start_with?(DATETIME_PREFIX) ? raw.delete_prefix(DATETIME_PREFIX) : raw
|
|
23
|
+
Time.iso8601(payload)
|
|
24
|
+
rescue ArgumentError
|
|
25
|
+
Time.at(0).utc
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def encode_qdate(value)
|
|
29
|
+
date = coerce_to_date(value)
|
|
30
|
+
"#{DATE_PREFIX}#{date.strftime('%Y-%m-%d')}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def decode_qdate(value)
|
|
34
|
+
raw = StringCodec.from_qt_text(value.to_s)
|
|
35
|
+
payload = raw.start_with?(DATE_PREFIX) ? raw.delete_prefix(DATE_PREFIX) : raw
|
|
36
|
+
Date.iso8601(payload)
|
|
37
|
+
rescue ArgumentError
|
|
38
|
+
Date.new(1970, 1, 1)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def encode_qtime(value)
|
|
42
|
+
time_string =
|
|
43
|
+
case value
|
|
44
|
+
when Time then value.strftime('%H:%M:%S')
|
|
45
|
+
else normalize_time_string(value.to_s)
|
|
46
|
+
end
|
|
47
|
+
"#{TIME_PREFIX}#{time_string}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def decode_qtime(value)
|
|
51
|
+
raw = StringCodec.from_qt_text(value.to_s)
|
|
52
|
+
payload = raw.start_with?(TIME_PREFIX) ? raw.delete_prefix(TIME_PREFIX) : raw
|
|
53
|
+
normalize_time_string(payload)
|
|
54
|
+
rescue ArgumentError
|
|
55
|
+
'00:00:00'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def decode_for_signal(signal_name, payload)
|
|
59
|
+
return nil if payload.nil?
|
|
60
|
+
|
|
61
|
+
signature = signal_name.to_s
|
|
62
|
+
if signature.start_with?('dateTimeChanged(')
|
|
63
|
+
return decode_qdatetime(payload)
|
|
64
|
+
end
|
|
65
|
+
if signature.start_with?('dateChanged(')
|
|
66
|
+
return decode_qdate(payload)
|
|
67
|
+
end
|
|
68
|
+
if signature.start_with?('timeChanged(')
|
|
69
|
+
return decode_qtime(payload)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
StringCodec.from_qt_text(payload)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def coerce_to_time(value)
|
|
76
|
+
return value if value.is_a?(Time)
|
|
77
|
+
return value.to_time if value.respond_to?(:to_time)
|
|
78
|
+
|
|
79
|
+
Time.iso8601(value.to_s)
|
|
80
|
+
rescue ArgumentError
|
|
81
|
+
Time.at(0).utc
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def coerce_to_date(value)
|
|
85
|
+
return value if value.is_a?(Date)
|
|
86
|
+
return value.to_date if value.respond_to?(:to_date)
|
|
87
|
+
|
|
88
|
+
Date.iso8601(value.to_s)
|
|
89
|
+
rescue ArgumentError
|
|
90
|
+
Date.new(1970, 1, 1)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def normalize_time_string(value)
|
|
94
|
+
raw = value.to_s.strip
|
|
95
|
+
raise ArgumentError, 'time is empty' if raw.empty?
|
|
96
|
+
|
|
97
|
+
return "#{raw}:00" if raw.match?(/\A\d{2}:\d{2}\z/)
|
|
98
|
+
return raw if raw.match?(/\A\d{2}:\d{2}:\d{2}\z/)
|
|
99
|
+
|
|
100
|
+
parsed = Time.parse(raw)
|
|
101
|
+
parsed.strftime('%H:%M:%S')
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/qt/errors.rb
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
# Event/signal subscription runtime backed by generated native callbacks.
|
|
5
|
+
module EventRuntime
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def on_event(widget, event_name, &block)
|
|
9
|
+
raise ArgumentError, 'pass block to on_event' unless block
|
|
10
|
+
|
|
11
|
+
ensure_native_bridge_ready!
|
|
12
|
+
ensure_event_callback!
|
|
13
|
+
|
|
14
|
+
event_type = event_type_for(event_name)
|
|
15
|
+
handle = widget_handle(widget) || raise(ArgumentError, 'widget handle is required')
|
|
16
|
+
|
|
17
|
+
@event_handlers ||= {}
|
|
18
|
+
((@event_handlers[handle.address] ||= {})[event_type] ||= []) << block
|
|
19
|
+
|
|
20
|
+
Qt::Native.watch_qobject_event(handle, event_type)
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def on_signal(widget, signal_name, &block)
|
|
25
|
+
raise ArgumentError, 'pass block to on_signal' unless block
|
|
26
|
+
|
|
27
|
+
ensure_native_bridge_ready!
|
|
28
|
+
ensure_signal_callback!
|
|
29
|
+
|
|
30
|
+
handle = widget_handle(widget) || raise(ArgumentError, 'widget handle is required')
|
|
31
|
+
per_signal = prepare_signal_registration(handle, signal_name)
|
|
32
|
+
per_signal[:blocks] << block
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def prepare_signal_registration(handle, signal_name)
|
|
37
|
+
signal_key = signal_name.to_s
|
|
38
|
+
raise ArgumentError, 'signal name is required' if signal_key.empty?
|
|
39
|
+
|
|
40
|
+
@signal_handlers ||= {}
|
|
41
|
+
per_signal = ((@signal_handlers[handle.address] ||= {})[signal_key] ||= { index: nil, blocks: [] })
|
|
42
|
+
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
|
|
47
|
+
end
|
|
48
|
+
per_signal
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def off_signal(widget, signal_name = nil)
|
|
52
|
+
ensure_native_bridge_ready!
|
|
53
|
+
handle = widget_handle(widget)
|
|
54
|
+
return false if handle.nil? || @signal_handlers.nil?
|
|
55
|
+
|
|
56
|
+
per_widget = @signal_handlers[handle.address]
|
|
57
|
+
return false if per_widget.nil?
|
|
58
|
+
|
|
59
|
+
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)
|
|
63
|
+
true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def off_event(widget, event_name = nil)
|
|
67
|
+
ensure_native_bridge_ready!
|
|
68
|
+
handle = widget_handle(widget)
|
|
69
|
+
return false if handle.nil? || @event_handlers.nil?
|
|
70
|
+
|
|
71
|
+
per_widget = @event_handlers[handle.address]
|
|
72
|
+
return false unless per_widget
|
|
73
|
+
|
|
74
|
+
off_event_for_widget(handle, per_widget, event_name)
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def event_type_for(event_name)
|
|
79
|
+
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]
|
|
87
|
+
raise ArgumentError, "unknown event: #{event_name.inspect}" unless event_type
|
|
88
|
+
|
|
89
|
+
event_type
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def ensure_event_callback!
|
|
93
|
+
return if @event_callback
|
|
94
|
+
|
|
95
|
+
@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 }
|
|
100
|
+
EventRuntimeDispatch.dispatch_event(@event_handlers, object_handle, event_type, payload)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
Qt::Native.set_event_callback(@event_callback)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def ensure_signal_callback!
|
|
107
|
+
return if @signal_callback
|
|
108
|
+
|
|
109
|
+
@signal_callback = FFI::Function.new(:void, %i[pointer int string]) do |object_handle, signal_index, payload|
|
|
110
|
+
normalized_payload = payload.nil? ? nil : Qt::StringCodec.from_qt_text(payload)
|
|
111
|
+
EventRuntimeDispatch.dispatch_signal(@signal_handlers, object_handle, signal_index, normalized_payload)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
Qt::Native.set_signal_callback(@signal_callback)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def widget_handle(widget)
|
|
118
|
+
return nil if widget.nil?
|
|
119
|
+
|
|
120
|
+
widget.respond_to?(:handle) ? widget.handle : widget
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def ensure_native_bridge_ready!
|
|
124
|
+
Qt::Native.ensure_loaded!
|
|
125
|
+
Qt::Native.define_bridge_wrappers!
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def off_event_for_widget(handle, per_widget, event_name)
|
|
129
|
+
if event_name
|
|
130
|
+
event_type = event_type_for(event_name)
|
|
131
|
+
per_widget.delete(event_type)
|
|
132
|
+
Qt::Native.unwatch_qobject_event(handle, event_type)
|
|
133
|
+
else
|
|
134
|
+
per_widget.each_key { |event_type| Qt::Native.unwatch_qobject_event(handle, event_type) }
|
|
135
|
+
@event_handlers.delete(handle.address)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Dispatch helpers for event/signal callbacks from the native bridge.
|
|
4
|
+
module Qt
|
|
5
|
+
# Dispatch helpers for event/signal callbacks from the native bridge.
|
|
6
|
+
module EventRuntimeDispatch
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def dispatch_event(event_handlers, object_handle, event_type, payload)
|
|
10
|
+
return unless object_handle && event_handlers
|
|
11
|
+
|
|
12
|
+
per_widget = event_handlers[object_handle.address]
|
|
13
|
+
return unless per_widget
|
|
14
|
+
|
|
15
|
+
handlers = per_widget[event_type]
|
|
16
|
+
return unless handlers && !handlers.empty?
|
|
17
|
+
|
|
18
|
+
handlers.each { |handler| handler.call(payload) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def dispatch_signal(signal_handlers, object_handle, signal_index, payload)
|
|
22
|
+
return unless object_handle && signal_handlers
|
|
23
|
+
|
|
24
|
+
per_widget = signal_handlers[object_handle.address]
|
|
25
|
+
return unless per_widget
|
|
26
|
+
|
|
27
|
+
per_widget.each do |signal_name, entry|
|
|
28
|
+
next unless entry[:index] == signal_index
|
|
29
|
+
|
|
30
|
+
typed_payload = Qt::DateTimeCodec.decode_for_signal(signal_name, payload)
|
|
31
|
+
entry[:blocks].each { |handler| handler.call(typed_payload) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# QObject-level event/signal helper methods mixed into generated classes.
|
|
4
|
+
module Qt
|
|
5
|
+
module EventRuntime
|
|
6
|
+
# QObject-level event/signal helper methods mixed into generated classes.
|
|
7
|
+
module QObjectMethods
|
|
8
|
+
def on(event_name, &block)
|
|
9
|
+
raise ArgumentError, 'pass block to on' unless block
|
|
10
|
+
|
|
11
|
+
EventRuntime.on_event(self, event_name, &block)
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
alias on_event on
|
|
15
|
+
|
|
16
|
+
def connect(signal_name, &block)
|
|
17
|
+
raise ArgumentError, 'pass block to connect' unless block
|
|
18
|
+
|
|
19
|
+
EventRuntime.on_signal(self, signal_name, &block)
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
alias on_signal connect
|
|
23
|
+
alias slot connect
|
|
24
|
+
|
|
25
|
+
def off(event_name = nil)
|
|
26
|
+
EventRuntime.off_event(self, event_name)
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
alias off_event off
|
|
30
|
+
|
|
31
|
+
def disconnect(signal_name = nil)
|
|
32
|
+
EventRuntime.off_signal(self, signal_name)
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
alias off_signal disconnect
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Backward-compatible alias for already-generated code.
|
|
39
|
+
WidgetMethods = QObjectMethods
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
module GeneratedConstantsRuntime
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def apply_generated_scoped_constants!(qt_module)
|
|
8
|
+
return unless qt_module.const_defined?(:GENERATED_SCOPED_CONSTANTS, false)
|
|
9
|
+
|
|
10
|
+
qt_module.const_get(:GENERATED_SCOPED_CONSTANTS, false).each do |owner_name, owner_constants|
|
|
11
|
+
next unless qt_module.const_defined?(owner_name, false)
|
|
12
|
+
|
|
13
|
+
owner = qt_module.const_get(owner_name, false)
|
|
14
|
+
owner_constants.each do |const_name, const_value|
|
|
15
|
+
next if owner.const_defined?(const_name, false)
|
|
16
|
+
|
|
17
|
+
owner.const_set(const_name, const_value)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def apply_key_aliases!(qt_module)
|
|
23
|
+
qt_module.constants(false).grep(/\AKey_[A-Za-z0-9_]+\z/).each do |source_name|
|
|
24
|
+
suffix = source_name.to_s.sub(/\AKey_/, '')
|
|
25
|
+
alias_name = "Key#{suffix.split('_').map(&:capitalize).join}"
|
|
26
|
+
next unless alias_name.match?(/\A[A-Z][A-Za-z0-9_]*\z/)
|
|
27
|
+
next if qt_module.const_defined?(alias_name, false)
|
|
28
|
+
|
|
29
|
+
qt_module.const_set(alias_name, qt_module.const_get(source_name, false))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
# Common object inspection formatting for Qt wrapper instances.
|
|
5
|
+
module Inspectable
|
|
6
|
+
def q_inspect_property_values
|
|
7
|
+
property_values = {}
|
|
8
|
+
self.class::QT_API_PROPERTIES.each do |property|
|
|
9
|
+
property_values[property] = public_send(property)
|
|
10
|
+
rescue StandardError => e
|
|
11
|
+
property_values[property] = { error: e.class.name, message: e.message }
|
|
12
|
+
end
|
|
13
|
+
property_values
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def q_inspect
|
|
17
|
+
{
|
|
18
|
+
qt_class: self.class::QT_CLASS,
|
|
19
|
+
ruby_class: self.class.name,
|
|
20
|
+
handle: @handle,
|
|
21
|
+
qt_methods: self.class::QT_API_QT_METHODS,
|
|
22
|
+
ruby_methods: self.class::QT_API_RUBY_METHODS,
|
|
23
|
+
properties: q_inspect_property_values
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
alias qt_inspect q_inspect
|
|
27
|
+
alias to_h q_inspect
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
# Conversion helpers for QKeySequence bridge arguments.
|
|
5
|
+
module KeySequenceCodec
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def encode(value)
|
|
9
|
+
return '' if value.nil?
|
|
10
|
+
return Qt::StringCodec.to_qt_text(value) if value.is_a?(String)
|
|
11
|
+
|
|
12
|
+
if value.respond_to?(:to_string)
|
|
13
|
+
return Qt::StringCodec.to_qt_text(value.to_string)
|
|
14
|
+
end
|
|
15
|
+
if value.respond_to?(:toString)
|
|
16
|
+
return Qt::StringCodec.to_qt_text(value.toString)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Qt::StringCodec.to_qt_text(value.to_s)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/qt/native.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
# Thin FFI access layer for generated bridge entry points.
|
|
5
|
+
module Native
|
|
6
|
+
require 'ffi'
|
|
7
|
+
|
|
8
|
+
ROOT = File.expand_path('../..', __dir__)
|
|
9
|
+
GENERATED_API = File.join(ROOT, 'build', 'generated', 'bridge_api.rb')
|
|
10
|
+
|
|
11
|
+
require GENERATED_API if File.exist?(GENERATED_API)
|
|
12
|
+
|
|
13
|
+
COERCERS = {
|
|
14
|
+
string: :to_s.to_proc,
|
|
15
|
+
int: ->(value) { Integer(value) },
|
|
16
|
+
bool: ->(value) { !value.nil? && value != false },
|
|
17
|
+
pointer: lambda { |value|
|
|
18
|
+
return nil if value.nil?
|
|
19
|
+
|
|
20
|
+
value.respond_to?(:handle) ? value.handle : value
|
|
21
|
+
}
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def available?
|
|
27
|
+
return @available unless @available.nil?
|
|
28
|
+
|
|
29
|
+
@available = Bridge.load! && Bridge.loaded?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ensure_loaded!
|
|
33
|
+
return if available?
|
|
34
|
+
|
|
35
|
+
detail = Bridge.load_error ? " (#{Bridge.load_error.message})" : ''
|
|
36
|
+
raise NativeExtensionError,
|
|
37
|
+
"Qt bridge is not available. Build it with: bundle exec rake compile#{detail}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def define_bridge_wrappers!
|
|
41
|
+
return if @bridge_wrappers_defined
|
|
42
|
+
return unless defined?(Qt::BridgeAPI::FUNCTIONS)
|
|
43
|
+
|
|
44
|
+
Qt::BridgeAPI::FUNCTIONS.each do |fn|
|
|
45
|
+
define_bridge_wrapper(fn)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@bridge_wrappers_defined = true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def define_bridge_wrapper(function_spec)
|
|
52
|
+
native_name = function_spec[:name].to_s.sub(/\Aqt_ruby_/, '')
|
|
53
|
+
signature = function_spec[:args]
|
|
54
|
+
bridge_name = function_spec[:name]
|
|
55
|
+
|
|
56
|
+
define_singleton_method(native_name) do |*args|
|
|
57
|
+
ensure_loaded!
|
|
58
|
+
normalized = normalize_bridge_args(args, signature)
|
|
59
|
+
converted = coerce_bridge_args(normalized, signature)
|
|
60
|
+
Bridge.public_send(bridge_name, *converted)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def normalize_bridge_args(args, signature)
|
|
65
|
+
if args.length < signature.length
|
|
66
|
+
ensure_missing_args_optional!(args, signature)
|
|
67
|
+
|
|
68
|
+
return args + ([nil] * (signature.length - args.length))
|
|
69
|
+
end
|
|
70
|
+
raise_wrong_arity(args.length, signature.length) if args.length > signature.length
|
|
71
|
+
|
|
72
|
+
args
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ensure_missing_args_optional!(args, signature)
|
|
76
|
+
missing = signature[args.length..]
|
|
77
|
+
raise_wrong_arity(args.length, signature.length) unless missing.all? { |type| type == :pointer }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def raise_wrong_arity(given, expected)
|
|
81
|
+
raise ArgumentError, "wrong number of arguments (given #{given}, expected #{expected})"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def coerce_bridge_args(args, signature)
|
|
85
|
+
args.zip(signature).map do |value, type|
|
|
86
|
+
coercer = COERCERS[type]
|
|
87
|
+
coercer ? coercer.call(value) : value
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
define_bridge_wrappers!
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Qt
|
|
4
|
+
# Backward-compatible QShortcut#set_keys handling for QKeySequence inputs.
|
|
5
|
+
module ShortcutCompat
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def key_sequence_like?(value)
|
|
9
|
+
value.is_a?(String) || value.respond_to?(:to_string) || value.respond_to?(:toString)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if defined?(Qt::QShortcut)
|
|
15
|
+
module Qt
|
|
16
|
+
class QShortcut
|
|
17
|
+
if instance_methods(false).include?(:set_keys) && !instance_methods(false).include?(:set_keys_without_qkeysequence_compat)
|
|
18
|
+
alias_method :set_keys_without_qkeysequence_compat, :set_keys
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set_keys(value)
|
|
22
|
+
if respond_to?(:set_key) && ShortcutCompat.key_sequence_like?(value)
|
|
23
|
+
set_key(value)
|
|
24
|
+
else
|
|
25
|
+
set_keys_without_qkeysequence_compat(value)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|