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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +27 -0
  3. data/README.md +303 -0
  4. data/Rakefile +94 -0
  5. data/examples/development_ordered_demos/01_dsl_hello.rb +22 -0
  6. data/examples/development_ordered_demos/02_live_layout_console.rb +137 -0
  7. data/examples/development_ordered_demos/03_component_showcase.rb +235 -0
  8. data/examples/development_ordered_demos/04_paint_simple.rb +147 -0
  9. data/examples/development_ordered_demos/05_tetris_simple.rb +295 -0
  10. data/examples/development_ordered_demos/06_timetrap_clockify.rb +759 -0
  11. data/examples/development_ordered_demos/07_peek_like_recorder.rb +597 -0
  12. data/examples/qtproject/widgets/itemviews/spreadsheet/main.rb +252 -0
  13. data/examples/qtproject/widgets/widgetsgallery/main.rb +184 -0
  14. data/ext/qt_ruby_bridge/extconf.rb +75 -0
  15. data/ext/qt_ruby_bridge/qt_ruby_runtime.hpp +23 -0
  16. data/ext/qt_ruby_bridge/runtime_events.cpp +408 -0
  17. data/ext/qt_ruby_bridge/runtime_signals.cpp +212 -0
  18. data/lib/qt/application_lifecycle.rb +44 -0
  19. data/lib/qt/bridge.rb +95 -0
  20. data/lib/qt/children_tracking.rb +15 -0
  21. data/lib/qt/constants.rb +10 -0
  22. data/lib/qt/date_time_codec.rb +104 -0
  23. data/lib/qt/errors.rb +6 -0
  24. data/lib/qt/event_runtime.rb +139 -0
  25. data/lib/qt/event_runtime_dispatch.rb +35 -0
  26. data/lib/qt/event_runtime_qobject_methods.rb +41 -0
  27. data/lib/qt/generated_constants_runtime.rb +33 -0
  28. data/lib/qt/inspectable.rb +29 -0
  29. data/lib/qt/key_sequence_codec.rb +22 -0
  30. data/lib/qt/native.rb +93 -0
  31. data/lib/qt/shortcut_compat.rb +30 -0
  32. data/lib/qt/string_codec.rb +44 -0
  33. data/lib/qt/variant_codec.rb +78 -0
  34. data/lib/qt/version.rb +5 -0
  35. data/lib/qt.rb +47 -0
  36. data/scripts/generate_bridge/ast_introspection.rb +267 -0
  37. data/scripts/generate_bridge/auto_method_spec_resolver.rb +37 -0
  38. data/scripts/generate_bridge/auto_methods.rb +438 -0
  39. data/scripts/generate_bridge/core_utils.rb +114 -0
  40. data/scripts/generate_bridge/cpp_method_return_emitter.rb +93 -0
  41. data/scripts/generate_bridge/ffi_api.rb +46 -0
  42. data/scripts/generate_bridge/free_function_specs.rb +289 -0
  43. data/scripts/generate_bridge/spec_discovery.rb +313 -0
  44. data/scripts/generate_bridge.rb +1113 -0
  45. metadata +99 -0
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../../../../lib', __dir__))
4
+ require 'qt'
5
+
6
+ WINDOW_W = 980
7
+ WINDOW_H = 660
8
+ COLS = 6
9
+
10
+ HEADERS = ['Item', 'Date', 'Price', 'Currency', 'Ex. Rate', 'NOK'].freeze
11
+ ROWS_DATA = [
12
+ ['AirportBus', '15/6/2006', 150, 'NOK', 1],
13
+ ['Flight (Munich)', '15/6/2006', 2350, 'NOK', 1],
14
+ ['Lunch', '15/6/2006', -14, 'EUR', 8],
15
+ ['Flight (LA)', '21/5/2006', 980, 'EUR', 8],
16
+ ['Taxi', '16/6/2006', 5, 'USD', 7],
17
+ ['Dinner', '16/6/2006', 120, 'USD', 7],
18
+ ['Hotel', '16/6/2006', 300, 'USD', 7],
19
+ ['Flight (Oslo)', '18/6/2006', 1240, 'NOK', 1]
20
+ ].freeze
21
+
22
+ ROWS = ROWS_DATA.length + 1
23
+ TOTAL_ROW = ROWS - 1
24
+
25
+ BG = 'background-color: #0b111b; border: 1px solid #1f2937;'
26
+ PANEL = 'background-color: #0f172a; border: 1px solid #334155; color: #e2e8f0;'
27
+ TITLE = 'background-color: #111827; border: 1px solid #374151; color: #f8fafc; font-size: 16px; font-weight: 800;'
28
+ INPUT = 'background-color: #0b1220; border: 1px solid #334155; color: #f8fafc; font-size: 12px;'
29
+ BTN = 'background-color: #111827; border: 1px solid #64748b; color: #e2e8f0; font-size: 12px; font-weight: 700;'
30
+ BTN_ACTIVE = 'background-color: #1d4ed8; border: 2px solid #60a5fa; color: #f8fafc; font-size: 12px; font-weight: 800;'
31
+
32
+ TABLE_QSS = [
33
+ 'QTableWidget {',
34
+ ' background-color: #0b1220;',
35
+ ' color: #f8fafc;',
36
+ ' border: 1px solid #334155;',
37
+ ' gridline-color: #1f2937;',
38
+ '}',
39
+ 'QHeaderView::section {',
40
+ ' background-color: #3f3f46;',
41
+ ' color: #f8fafc;',
42
+ ' border: 1px solid #4b5563;',
43
+ ' font-weight: 700;',
44
+ '}',
45
+ 'QTableCornerButton::section {',
46
+ ' background-color: #3f3f46;',
47
+ ' border: 1px solid #4b5563;',
48
+ '}'
49
+ ].join("\n").freeze
50
+
51
+ app = QApplication.new(0, [])
52
+ window = QWidget.new do |w|
53
+ w.set_window_title('Spreadsheet')
54
+ w.set_geometry(70, 60, WINDOW_W, WINDOW_H)
55
+ end
56
+
57
+ bg = QLabel.new(window)
58
+ panel = QLabel.new(window)
59
+
60
+ title = QLabel.new(window)
61
+ title.set_alignment(Qt::AlignCenter)
62
+ title.set_text('Spreadsheet')
63
+
64
+ cell_label = QLabel.new(window)
65
+ cell_label.set_alignment(Qt::AlignCenter)
66
+ cell_label.set_text('Cell: (A1)')
67
+
68
+ formula = QLineEdit.new(window)
69
+ formula.set_placeholder_text('Cell value')
70
+
71
+ apply_btn = QPushButton.new(window)
72
+ apply_btn.set_text('APPLY')
73
+
74
+ recalc_btn = QPushButton.new(window)
75
+ recalc_btn.set_text('RECALCULATE')
76
+
77
+ sheet = QTableWidget.new(window)
78
+ sheet.set_row_count(ROWS)
79
+ sheet.set_column_count(COLS)
80
+ sheet.set_vertical_scroll_mode(Qt::ScrollPerPixel)
81
+
82
+ [180, 160, 120, 140, 120, 120].each_with_index do |width, col|
83
+ sheet.set_column_width(col, width)
84
+ end
85
+
86
+ headers = []
87
+ HEADERS.each_with_index do |text, col|
88
+ item = QTableWidgetItem.new
89
+ item.set_text(text)
90
+ item.set_text_alignment(Qt::AlignCenter)
91
+ sheet.set_horizontal_header_item(col, item)
92
+ headers << item
93
+ end
94
+
95
+ matrix = Array.new(ROWS) { Array.new(COLS) }
96
+
97
+ ROWS_DATA.each_with_index do |row_data, row|
98
+ COLS.times do |col|
99
+ item = QTableWidgetItem.new
100
+ value = case col
101
+ when 0 then row_data[0]
102
+ when 1 then row_data[1]
103
+ when 2 then row_data[2]
104
+ when 3 then row_data[3]
105
+ when 4 then row_data[4]
106
+ else 0
107
+ end
108
+ item.set_text(value.to_s)
109
+ item.set_text_alignment(Qt::AlignCenter)
110
+ sheet.set_item(row, col, item)
111
+ matrix[row][col] = item
112
+ end
113
+ end
114
+
115
+ COLS.times do |col|
116
+ item = QTableWidgetItem.new
117
+ value = if col.zero?
118
+ 'Total:'
119
+ elsif col == 5
120
+ '0'
121
+ else
122
+ 'None'
123
+ end
124
+ item.set_text(value)
125
+ item.set_text_alignment(Qt::AlignCenter)
126
+ sheet.set_item(TOTAL_ROW, col, item)
127
+ matrix[TOTAL_ROW][col] = item
128
+ end
129
+
130
+ int_value = lambda do |text|
131
+ Integer(text.to_s.strip)
132
+ rescue StandardError
133
+ 0
134
+ end
135
+
136
+ recompute = lambda do
137
+ total = 0
138
+ ROWS_DATA.length.times do |row|
139
+ price = int_value.call(matrix[row][2].text)
140
+ rate = int_value.call(matrix[row][4].text)
141
+ nok = price * rate
142
+ matrix[row][5].set_text(nok.to_s)
143
+ total += nok
144
+ end
145
+
146
+ matrix[TOTAL_ROW][5].set_text(total.to_s)
147
+ end
148
+
149
+ col_label = lambda do |col|
150
+ letters = +''
151
+ n = col + 1
152
+ while n.positive?
153
+ n -= 1
154
+ letters.prepend((65 + (n % 26)).chr)
155
+ n /= 26
156
+ end
157
+ letters
158
+ end
159
+
160
+ sync_formula_with_current = lambda do
161
+ row = sheet.current_row
162
+ col = sheet.current_column
163
+ return if row.negative? || col.negative?
164
+
165
+ item = matrix[row][col]
166
+ return unless item
167
+
168
+ cell_label.set_text("Cell: (#{col_label.call(col)}#{row + 1})")
169
+ formula.set_text(item.text.to_s)
170
+ end
171
+
172
+ flash = lambda do |button|
173
+ button.set_style_sheet(BTN_ACTIVE)
174
+ QApplication.process_events
175
+ sleep(0.03)
176
+ button.set_style_sheet(BTN)
177
+ end
178
+
179
+ apply_current_cell = lambda do
180
+ row = sheet.current_row
181
+ col = sheet.current_column
182
+ return if row.negative? || col.negative?
183
+
184
+ item = matrix[row][col]
185
+ return unless item
186
+
187
+ item.set_text(formula.text.to_s)
188
+ recompute.call
189
+ sync_formula_with_current.call
190
+ end
191
+
192
+ apply_btn.connect('clicked') do
193
+ flash.call(apply_btn)
194
+ apply_current_cell.call
195
+ end
196
+
197
+ formula.connect('returnPressed') { apply_current_cell.call }
198
+
199
+ recalc_btn.connect('clicked') do
200
+ flash.call(recalc_btn)
201
+ recompute.call
202
+ sync_formula_with_current.call
203
+ end
204
+
205
+ sheet.on(:mouse_button_release) { sync_formula_with_current.call }
206
+ sheet.on(:key_release) { sync_formula_with_current.call }
207
+
208
+ layout_ui = lambda do
209
+ ww = window.width
210
+ wh = window.height
211
+
212
+ bg.set_geometry(0, 0, ww, wh)
213
+ panel.set_geometry(16, 16, ww - 32, wh - 32)
214
+
215
+ title.set_geometry(32, 24, ww - 64, 36)
216
+
217
+ cell_label.set_geometry(32, 68, 130, 32)
218
+ formula.set_geometry(170, 68, ww - 372, 32)
219
+ apply_btn.set_geometry(ww - 194, 68, 74, 32)
220
+ recalc_btn.set_geometry(ww - 112, 68, 80, 32)
221
+
222
+ sheet.set_geometry(32, 112, ww - 64, wh - 144)
223
+ end
224
+
225
+ apply_styles = lambda do
226
+ bg.set_style_sheet(BG)
227
+ panel.set_style_sheet(PANEL)
228
+ title.set_style_sheet(TITLE)
229
+ cell_label.set_style_sheet(PANEL)
230
+ formula.set_style_sheet(INPUT)
231
+ apply_btn.set_style_sheet(BTN)
232
+ recalc_btn.set_style_sheet(BTN)
233
+ sheet.set_style_sheet(TABLE_QSS)
234
+ end
235
+
236
+ window.on(:resize) { layout_ui.call }
237
+
238
+ recompute.call
239
+ apply_styles.call
240
+ layout_ui.call
241
+ sheet.set_current_cell(0, 0)
242
+ sync_formula_with_current.call
243
+ window.show
244
+ QApplication.process_events
245
+
246
+ # TODO: Replace manual process_events loop with app.exec + QTimer.
247
+ while window.is_visible != 0
248
+ QApplication.process_events
249
+ sleep(0.01)
250
+ end
251
+
252
+ app.dispose
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../../../lib', __dir__))
4
+ require 'qt'
5
+
6
+ WINDOW_W = 980
7
+ WINDOW_H = 640
8
+ SIDEBAR_W = 300
9
+
10
+ THEMES = {
11
+ light: {
12
+ root: 'background-color: #f5f7fb; border: 1px solid #dbe1ec;',
13
+ card: 'background-color: #ffffff; border: 1px solid #d5dce8; color: #0f172a;',
14
+ title: 'background-color: #ffffff; border: 1px solid #cbd5e1; color: #0f172a; font-size: 17px; font-weight: 800;',
15
+ button: 'background-color: #ffffff; border: 1px solid #94a3b8; color: #0f172a; font-size: 12px; font-weight: 700;',
16
+ button_active: 'background-color: #dbeafe; border: 2px solid #2563eb; color: #0f172a; ' \
17
+ 'font-size: 12px; font-weight: 800;',
18
+ input: 'background-color: #ffffff; border: 1px solid #a8b3c6; color: #0f172a; font-size: 12px;'
19
+ },
20
+ dark: {
21
+ root: 'background-color: #0b1220; border: 1px solid #1f2a44;',
22
+ card: 'background-color: #101a2d; border: 1px solid #334155; color: #e2e8f0;',
23
+ title: 'background-color: #0f172a; border: 1px solid #334155; color: #f8fafc; font-size: 17px; font-weight: 800;',
24
+ button: 'background-color: #111827; border: 1px solid #475569; color: #e2e8f0; font-size: 12px; font-weight: 700;',
25
+ button_active: 'background-color: #1d4ed8; border: 2px solid #60a5fa; color: #f8fafc; ' \
26
+ 'font-size: 12px; font-weight: 800;',
27
+ input: 'background-color: #0b1220; border: 1px solid #475569; color: #e2e8f0; font-size: 12px;'
28
+ }
29
+ }.freeze
30
+
31
+ app = QApplication.new(0, [])
32
+ window = QWidget.new do |w|
33
+ w.set_window_title('Qt Ruby Widget Gallery Lite (inspired by PySide widgetsgallery)')
34
+ w.set_geometry(70, 60, WINDOW_W, WINDOW_H)
35
+ end
36
+
37
+ root = QLabel.new(window)
38
+ left = QLabel.new(window)
39
+ right = QLabel.new(window)
40
+
41
+ headline = QLabel.new(window)
42
+ headline.set_alignment(Qt::AlignCenter)
43
+ headline.set_text('Widget Gallery Lite')
44
+
45
+ subtitle = QLabel.new(window)
46
+ subtitle.set_alignment(Qt::AlignCenter)
47
+ subtitle.set_text('Inspired by PySide widgetsgallery: theme toggle, interactive controls, status area')
48
+
49
+ name_label = QLabel.new(window)
50
+ name_label.set_alignment(Qt::AlignCenter)
51
+ name_label.set_text('Name input')
52
+
53
+ name_input = QLineEdit.new(window)
54
+ name_input.set_placeholder_text('Type your name...')
55
+
56
+ name_echo = QLabel.new(window)
57
+ name_echo.set_alignment(Qt::AlignCenter)
58
+ name_echo.set_text('Hello, stranger')
59
+
60
+ counter_label = QLabel.new(window)
61
+ counter_label.set_alignment(Qt::AlignCenter)
62
+ counter_label.set_text('Counter: 0')
63
+
64
+ counter = 0
65
+
66
+ actions = [
67
+ { key: :plus, text: 'INCREMENT' },
68
+ { key: :minus, text: 'DECREMENT' },
69
+ { key: :reset, text: 'RESET' },
70
+ { key: :theme, text: 'TOGGLE THEME' }
71
+ ]
72
+
73
+ actions.each do |action|
74
+ action[:view] = QPushButton.new(window)
75
+ action[:view].set_text(action[:text])
76
+ end
77
+
78
+ status = QLabel.new(window)
79
+ status.set_alignment(Qt::AlignCenter)
80
+ status.set_text('Ready')
81
+
82
+ current_theme = :light
83
+
84
+ layout_ui = lambda do
85
+ ww = window.width
86
+ wh = window.height
87
+ sidebar_x = ww - SIDEBAR_W
88
+
89
+ root.set_geometry(0, 0, ww, wh)
90
+
91
+ left.set_geometry(16, 16, ww - SIDEBAR_W - 32, wh - 32)
92
+ headline.set_geometry(34, 34, ww - SIDEBAR_W - 68, 40)
93
+ subtitle.set_geometry(34, 82, ww - SIDEBAR_W - 68, 34)
94
+
95
+ name_label.set_geometry(34, 142, ww - SIDEBAR_W - 68, 30)
96
+ name_input.set_geometry(34, 178, ww - SIDEBAR_W - 68, 38)
97
+ name_echo.set_geometry(34, 224, ww - SIDEBAR_W - 68, 38)
98
+ counter_label.set_geometry(34, 272, ww - SIDEBAR_W - 68, 38)
99
+
100
+ right.set_geometry(sidebar_x, 0, SIDEBAR_W, wh)
101
+ actions.each_with_index do |action, idx|
102
+ action[:view].set_geometry(sidebar_x + 18, 28 + (idx * 52), SIDEBAR_W - 36, 40)
103
+ end
104
+ status.set_geometry(sidebar_x + 18, wh - 84, SIDEBAR_W - 36, 56)
105
+ end
106
+
107
+ apply_theme = lambda do
108
+ theme = THEMES.fetch(current_theme)
109
+ root.set_style_sheet(theme[:root])
110
+ left.set_style_sheet(theme[:card])
111
+ right.set_style_sheet(theme[:card])
112
+ headline.set_style_sheet(theme[:title])
113
+ subtitle.set_style_sheet(theme[:card])
114
+ name_label.set_style_sheet(theme[:card])
115
+ name_input.set_style_sheet(theme[:input])
116
+ name_echo.set_style_sheet(theme[:card])
117
+ counter_label.set_style_sheet(theme[:card])
118
+ status.set_style_sheet(theme[:card])
119
+ actions.each { |a| a[:view].set_style_sheet(theme[:button]) }
120
+ end
121
+
122
+ flash = lambda do |button|
123
+ theme = THEMES.fetch(current_theme)
124
+ button.set_style_sheet(theme[:button_active])
125
+ QApplication.process_events
126
+ sleep(0.03)
127
+ button.set_style_sheet(theme[:button])
128
+ end
129
+
130
+ set_name_echo = lambda do
131
+ text = name_input.text.to_s.strip
132
+ name_echo.set_text(text.empty? ? 'Hello, stranger' : "Hello, #{text}")
133
+ end
134
+
135
+ set_counter = lambda do
136
+ counter_label.set_text("Counter: #{counter}")
137
+ end
138
+
139
+ perform = lambda do |key, view|
140
+ flash.call(view)
141
+
142
+ case key
143
+ when :plus
144
+ counter += 1
145
+ set_counter.call
146
+ status.set_text('Incremented')
147
+ when :minus
148
+ counter -= 1
149
+ set_counter.call
150
+ status.set_text('Decremented')
151
+ when :reset
152
+ counter = 0
153
+ set_counter.call
154
+ name_input.set_text('')
155
+ set_name_echo.call
156
+ status.set_text('Reset all')
157
+ when :theme
158
+ current_theme = (current_theme == :light ? :dark : :light)
159
+ apply_theme.call
160
+ status.set_text("Theme: #{current_theme}")
161
+ end
162
+ end
163
+
164
+ actions.each do |action|
165
+ action[:view].connect('clicked') do
166
+ perform.call(action[:key], action[:view])
167
+ end
168
+ end
169
+
170
+ name_input.connect('textChanged') { set_name_echo.call }
171
+ window.on(:resize) { layout_ui.call }
172
+
173
+ layout_ui.call
174
+ apply_theme.call
175
+ window.show
176
+ QApplication.process_events
177
+
178
+ # TODO: Replace manual process_events loop with app.exec + QTimer.
179
+ while window.is_visible != 0
180
+ QApplication.process_events
181
+ sleep(0.01)
182
+ end
183
+
184
+ app.dispose
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+ require 'fileutils'
5
+
6
+ PKG_CONFIG = RbConfig::CONFIG['PKG_CONFIG'] || 'pkg-config'
7
+ QT_PACKAGES = %w[Qt6Core Qt6Gui Qt6Widgets].freeze
8
+ MINIMUM_QT_VERSION = Gem::Version.new('6.10.0')
9
+
10
+ def pkg_config(*)
11
+ system(PKG_CONFIG, *, out: File::NULL, err: File::NULL)
12
+ end
13
+
14
+ def pkg_config_capture(*args)
15
+ `#{[PKG_CONFIG, *args].join(' ')}`.strip
16
+ end
17
+
18
+ abort 'pkg-config is required to build qt-ruby bridge.' unless find_executable(PKG_CONFIG)
19
+
20
+ generator = File.expand_path('../../scripts/generate_bridge.rb', __dir__)
21
+ abort "Generator script not found: #{generator}" unless File.exist?(generator)
22
+
23
+ generator_env = {}
24
+ scope = ENV.fetch('QT_RUBY_SCOPE', nil)
25
+ generator_env['QT_RUBY_SCOPE'] = scope if scope && !scope.empty?
26
+ abort 'Failed to generate Qt bridge files.' unless system(generator_env, RbConfig.ruby, generator)
27
+
28
+ missing = QT_PACKAGES.reject { |pkg| pkg_config('--exists', pkg) }
29
+ abort "Missing Qt packages: #{missing.join(', ')}" unless missing.empty?
30
+
31
+ qt_version_str = pkg_config_capture('--modversion', 'Qt6Core')
32
+ qt_version = Gem::Version.new(qt_version_str)
33
+ abort "Qt version #{qt_version} is too old. Require >= #{MINIMUM_QT_VERSION}." if qt_version < MINIMUM_QT_VERSION
34
+
35
+ cflags = pkg_config_capture('--cflags', *QT_PACKAGES)
36
+ libs = pkg_config_capture('--libs', *QT_PACKAGES)
37
+ generated_cpp = if File.exist?('qt_ruby_bridge.cpp')
38
+ File.expand_path('qt_ruby_bridge.cpp')
39
+ else
40
+ File.expand_path('../../build/generated/qt_ruby_bridge.cpp', __dir__)
41
+ end
42
+ runtime_hpp = File.expand_path('../../ext/qt_ruby_bridge/qt_ruby_runtime.hpp', __dir__)
43
+ runtime_cpp_files = %w[
44
+ runtime_events.cpp
45
+ runtime_signals.cpp
46
+ ].map { |name| File.expand_path("../../ext/qt_ruby_bridge/#{name}", __dir__) }
47
+
48
+ unless File.exist?(generated_cpp)
49
+ abort "Generated source not found: #{generated_cpp}. Run: ruby scripts/generate_bridge.rb"
50
+ end
51
+ abort "Runtime header not found: #{runtime_hpp}" unless File.exist?(runtime_hpp)
52
+ missing_runtime = runtime_cpp_files.reject { |path| File.exist?(path) }
53
+ abort "Runtime source not found: #{missing_runtime.join(', ')}" unless missing_runtime.empty?
54
+
55
+ local_cpp = File.expand_path('qt_ruby_bridge.cpp')
56
+ FileUtils.cp(generated_cpp, local_cpp) unless File.exist?(local_cpp) && File.identical?(generated_cpp, local_cpp)
57
+ runtime_cpp_files.each do |runtime_cpp|
58
+ local_runtime_cpp = File.expand_path(File.basename(runtime_cpp))
59
+ unless File.exist?(local_runtime_cpp) && File.identical?(runtime_cpp, local_runtime_cpp)
60
+ FileUtils.cp(runtime_cpp, local_runtime_cpp)
61
+ end
62
+ end
63
+ local_runtime_hpp = File.expand_path('qt_ruby_runtime.hpp')
64
+ unless File.exist?(local_runtime_hpp) && File.identical?(runtime_hpp, local_runtime_hpp)
65
+ FileUtils.cp(runtime_hpp, local_runtime_hpp)
66
+ end
67
+
68
+ # mkmf uses these global variables for compiler/linker/source configuration.
69
+ # rubocop:disable Style/GlobalVars
70
+ $CXXFLAGS = "#{$CXXFLAGS} #{cflags} -std=c++17"
71
+ $LDFLAGS = "#{$LDFLAGS} #{libs}"
72
+ $srcs = ['qt_ruby_bridge.cpp', *runtime_cpp_files.map { |f| File.basename(f) }]
73
+ # rubocop:enable Style/GlobalVars
74
+
75
+ create_makefile('qt/qt_ruby_bridge')
@@ -0,0 +1,23 @@
1
+ #pragma once
2
+
3
+ #include <QApplication>
4
+
5
+ namespace QtRubyRuntime {
6
+ using EventCallback = void (*)(void*, int, int, int, int, int);
7
+ using SignalCallback = void (*)(void*, int, const char*);
8
+
9
+ // Creates/owns the singleton QApplication used by the Ruby runtime bridge.
10
+ // The implementation records the creating thread as GUI thread for shutdown checks.
11
+ QApplication* qapplication_new(const char* argv0);
12
+ // Performs guarded QApplication teardown.
13
+ // Returns false when teardown is rejected by runtime safety checks.
14
+ bool qapplication_delete(void* app_handle);
15
+
16
+ void set_event_callback(void* callback_ptr);
17
+ void watch_qobject_event(void* object_handle, int event_type);
18
+ void unwatch_qobject_event(void* object_handle, int event_type);
19
+
20
+ void set_signal_callback(void* callback_ptr);
21
+ int qobject_connect_signal(void* object_handle, const char* signal_name);
22
+ int qobject_disconnect_signal(void* object_handle, const char* signal_name);
23
+ } // namespace QtRubyRuntime