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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6d1a405c62cfb867d7799098163f4dff1122b3c92aa065ee0397ca499c0c4137
|
|
4
|
+
data.tar.gz: 7d731f1d727f82a96d1e1f8b910aa953f0d727bcc0ee2026e8da5ba3e536ad67
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0bcc6466742ddd75c5e81d38ccaf77b8767092f16de9abc4206735d5b7a34d8af2223c0a90efe67333f91aa7adca6f210901b121c32e6b8c7d1108a618065dc7
|
|
7
|
+
data.tar.gz: 1d55595b8845d119bbc11f60518281b1fea8ef5f3aa55698b174bf4e63d31e39d8771515c9980fc5cfe1d593a362193acd2159432a5be4b9e77218b870c78385
|
data/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
SPDX-License-Identifier: BSD-2-Clause
|
|
2
|
+
|
|
3
|
+
BSD 2-Clause License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026, Maksim Veynberg
|
|
6
|
+
All rights reserved.
|
|
7
|
+
|
|
8
|
+
Redistribution and use in source and binary forms, with or without
|
|
9
|
+
modification, are permitted provided that the following conditions are met:
|
|
10
|
+
|
|
11
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
12
|
+
list of conditions and the following disclaimer.
|
|
13
|
+
|
|
14
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
15
|
+
this list of conditions and the following disclaimer in the documentation
|
|
16
|
+
and/or other materials provided with the distribution.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# qt
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Ruby-first Qt 6.10+ bridge.
|
|
9
|
+
|
|
10
|
+
Build real Qt Widgets apps in pure Ruby, mutate them live from IRB, and keep C/C++ surface minimal via generated bridge code from system Qt headers.
|
|
11
|
+
|
|
12
|
+
## Why It Hits Different
|
|
13
|
+
|
|
14
|
+
- Pure Ruby usage: no QML, no extra UI language.
|
|
15
|
+
- Real Qt power: `QApplication`, `QWidget`, `QLabel`, `QPushButton`, `QVBoxLayout`.
|
|
16
|
+
- Ruby ergonomics: Qt-style and snake_case/property style in parallel.
|
|
17
|
+
- Live GUI hacking: update widgets while the window is open.
|
|
18
|
+
- Generated bridge: API is derived from system Qt headers.
|
|
19
|
+
|
|
20
|
+
## 30-Second Wow
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
ruby examples/development_ordered_demos/02_live_layout_console.rb
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then in IRB:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
add_label("Release pipeline")
|
|
30
|
+
add_button("Run")
|
|
31
|
+
remove_last
|
|
32
|
+
|
|
33
|
+
gui { window.resize(1100, 700) }
|
|
34
|
+
items.last&.q_inspect
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Before -> After
|
|
38
|
+
|
|
39
|
+
Before (typical static run):
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
app = QApplication.new(0, [])
|
|
43
|
+
window = QWidget.new
|
|
44
|
+
window.show
|
|
45
|
+
app.exec
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
After (live dev loop):
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# app already running
|
|
52
|
+
add_label("Dynamic block")
|
|
53
|
+
add_button("Ship")
|
|
54
|
+
gui { window.set_window_title("Changed live") }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
### Requirements
|
|
60
|
+
|
|
61
|
+
- Ruby 3.1+
|
|
62
|
+
- Qt 6.10+ dev packages (`Qt6Core`, `Qt6Gui`, `Qt6Widgets` via `pkg-config`)
|
|
63
|
+
- C++17 compiler
|
|
64
|
+
|
|
65
|
+
Check Qt:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pkg-config --modversion Qt6Widgets
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Build from repo
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bundle install
|
|
75
|
+
bundle exec rake compile
|
|
76
|
+
bundle exec rake install
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`rake install` installs into your current Ruby environment (including active `rbenv` version).
|
|
80
|
+
`rake compile` builds the full bridge with `QT_RUBY_SCOPE=all` by default.
|
|
81
|
+
|
|
82
|
+
### Gem usage
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
gem install qt
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Hello Qt in Ruby
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
require 'qt'
|
|
92
|
+
|
|
93
|
+
app = QApplication.new(0, [])
|
|
94
|
+
|
|
95
|
+
window = QWidget.new do |w|
|
|
96
|
+
w.set_window_title('Qt Ruby App')
|
|
97
|
+
w.resize(800, 600)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
label = QLabel.new(window)
|
|
101
|
+
label.text = 'Hello from Ruby + Qt'
|
|
102
|
+
label.set_alignment(Qt::AlignCenter)
|
|
103
|
+
label.set_geometry(0, 0, 800, 600)
|
|
104
|
+
|
|
105
|
+
app.exec
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## API Style: Qt + Ruby
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
# Qt style
|
|
112
|
+
label.setText('A')
|
|
113
|
+
window.setWindowTitle('Main')
|
|
114
|
+
|
|
115
|
+
# Ruby style
|
|
116
|
+
label.text = 'B'
|
|
117
|
+
window.window_title = 'Main 2'
|
|
118
|
+
puts label.text
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API Compatibility Notes
|
|
122
|
+
|
|
123
|
+
Generated Ruby API is intentionally close to Qt API, but follows universal bridge policies.
|
|
124
|
+
|
|
125
|
+
- `snake_case` aliases are generated for Qt camelCase methods.
|
|
126
|
+
- Ruby keyword-safe renaming is applied when needed: `next` -> `next_`.
|
|
127
|
+
- Default C++ arguments are surfaced as optional Ruby arguments.
|
|
128
|
+
- Internal runtime name collisions are renamed consistently:
|
|
129
|
+
- Qt `handle(int)` is exposed as `handle_at(int)` because `handle` is used for native object pointer access.
|
|
130
|
+
- Property convenience API is generated from Qt setters/getters when available:
|
|
131
|
+
- `setText(...)` -> `text=(...)`, `text`.
|
|
132
|
+
- Runtime event/signal convenience methods are Ruby-layer helpers (not raw Qt method names):
|
|
133
|
+
- `on(event, &block)` / alias `on_event`
|
|
134
|
+
- `off(event = nil)` / alias `off_event`
|
|
135
|
+
- `connect(signal, &block)` / aliases `on_signal`, `slot`
|
|
136
|
+
- `disconnect(signal = nil)` / alias `off_signal`
|
|
137
|
+
- these helpers are mixed into generated `QObject` descendants (for example `QWidget`, `QPushButton`, `QTimer`)
|
|
138
|
+
- non-`QObject` value classes (`QIcon`, `QPixmap`, `QImage`) intentionally do not expose `connect`/`on`
|
|
139
|
+
- event delivery is target-first with nearest watched ancestor fallback for interactive events (mouse/key/focus/enter/leave)
|
|
140
|
+
- Introspection helpers are Ruby-layer helpers:
|
|
141
|
+
- `q_inspect`, aliases `qt_inspect`, `to_h`
|
|
142
|
+
- Top-level constant aliases are provided for convenience:
|
|
143
|
+
- `QApplication`, `QWidget`, `QLabel`, `QPushButton`, `QLineEdit`, `QVBoxLayout`, `QTableWidget`, `QTableWidgetItem`, `QScrollArea`
|
|
144
|
+
- Methods with unsupported signatures are skipped by policy:
|
|
145
|
+
- non-public, deprecated, operator/internal event hooks,
|
|
146
|
+
- non-FFI-safe argument/return types.
|
|
147
|
+
|
|
148
|
+
## Introspection
|
|
149
|
+
|
|
150
|
+
Every generated object exposes API snapshot helpers:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
label.q_inspect
|
|
154
|
+
label.qt_inspect
|
|
155
|
+
label.to_h
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Shape:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
{
|
|
162
|
+
qt_class: "QLabel",
|
|
163
|
+
ruby_class: "Qt::QLabel",
|
|
164
|
+
qt_methods: ["setText", "setAlignment", "text", ...],
|
|
165
|
+
ruby_methods: [:setText, :set_text, :text, ...],
|
|
166
|
+
properties: { text: "A", alignment: 129 }
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Examples
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
ruby examples/development_ordered_demos/01_dsl_hello.rb
|
|
174
|
+
ruby examples/development_ordered_demos/02_live_layout_console.rb
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
QObject signal example:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
timer = QTimer.new
|
|
181
|
+
timer.set_interval(1000)
|
|
182
|
+
timer.connect('timeout') { puts 'tick' }
|
|
183
|
+
timer.start
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Architecture
|
|
187
|
+
|
|
188
|
+
1. `scripts/generate_bridge.rb` reads Qt API from system headers.
|
|
189
|
+
2. Generates:
|
|
190
|
+
- `build/generated/qt_ruby_bridge.cpp`
|
|
191
|
+
- `build/generated/bridge_api.rb`
|
|
192
|
+
- `build/generated/widgets.rb`
|
|
193
|
+
3. Compiles native extension into `build/qt/qt_ruby_bridge.so`.
|
|
194
|
+
4. Ruby layer calls bridge functions via `ffi`.
|
|
195
|
+
|
|
196
|
+
Everything generated/build-related is under `build/` and should stay out of git.
|
|
197
|
+
|
|
198
|
+
## Layout
|
|
199
|
+
|
|
200
|
+
- `lib/qt` public Ruby API
|
|
201
|
+
- `scripts/generate_bridge.rb` AST-driven bridge generator
|
|
202
|
+
- `ext/qt_ruby_bridge` native extension entrypoint
|
|
203
|
+
- `build/generated` generated sources
|
|
204
|
+
- `build/qt` compiled bridge `.so`
|
|
205
|
+
- `examples` demos
|
|
206
|
+
- `test` tests
|
|
207
|
+
|
|
208
|
+
## Roadmap
|
|
209
|
+
|
|
210
|
+
### Done
|
|
211
|
+
|
|
212
|
+
- AST-driven generation with scope support: `QT_RUBY_SCOPE=widgets|qobject|all`
|
|
213
|
+
- default compile path switched to `all` (`widgets + qobject`)
|
|
214
|
+
- generated Qt inheritance in Ruby classes (including intermediate Qt wrappers)
|
|
215
|
+
- Qt-native event/signal runtime wired to Ruby at QObject level (`on`, `connect`, `disconnect`)
|
|
216
|
+
- `QTimer` available in generated API with `connect('timeout')` support
|
|
217
|
+
- `06_timetrap_clockify` moved to `app.exec` + `QTimer` update loop (no manual polling loop)
|
|
218
|
+
- QObject styling hooks exposed for QSS selectors:
|
|
219
|
+
- `setObjectName` / `object_name=`
|
|
220
|
+
- `setProperty` / `property` (via QVariant bridge codec)
|
|
221
|
+
- window icon support from generated API:
|
|
222
|
+
- `QIcon.new(path)`
|
|
223
|
+
- `QWidget#setWindowIcon` / `set_window_icon`
|
|
224
|
+
|
|
225
|
+
### Next
|
|
226
|
+
|
|
227
|
+
- typed signal payloads (not only raw/placeholder payload)
|
|
228
|
+
- richer QObject metaobject Ruby API (`meta_object`, methods/signatures/properties introspection)
|
|
229
|
+
- normalize signal naming rules for overloads and deterministic connect behavior
|
|
230
|
+
|
|
231
|
+
### Later
|
|
232
|
+
|
|
233
|
+
- expand generated surface for additional Qt modules (network, sql, xml, etc.) using the same generator policy
|
|
234
|
+
- packaging hardening for Linux/macOS (install/build paths, gem install reliability)
|
|
235
|
+
- CI matrix for Ruby/Qt combinations and scope modes (`widgets`, `qobject`, `all`)
|
|
236
|
+
- add performance checks for generator traversal and compile size/time regression tracking
|
|
237
|
+
|
|
238
|
+
## Development
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
bundle exec rake test
|
|
242
|
+
bundle exec rake compile
|
|
243
|
+
bundle exec rake rubocop
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Test Environment Variables
|
|
247
|
+
|
|
248
|
+
Tests force `QT_QPA_PLATFORM=offscreen` by default to avoid opening GUI windows.
|
|
249
|
+
|
|
250
|
+
- `QT_QPA_PLATFORM_FORCE_XCB=true`
|
|
251
|
+
- override test default and run with `QT_QPA_PLATFORM=xcb` (real X11 backend)
|
|
252
|
+
- `QT_RUBY_MANUAL_MODIFIERS=1`
|
|
253
|
+
- enable manual keyboard-modifier smoke test (`Ctrl/Shift` must be pressed during test window)
|
|
254
|
+
|
|
255
|
+
Examples:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# default headless test run
|
|
259
|
+
bundle exec rake test
|
|
260
|
+
|
|
261
|
+
# run tests on xcb backend
|
|
262
|
+
QT_QPA_PLATFORM_FORCE_XCB=true bundle exec rake test
|
|
263
|
+
|
|
264
|
+
# run only manual modifiers smoke test
|
|
265
|
+
QT_QPA_PLATFORM_FORCE_XCB=true QT_RUBY_MANUAL_MODIFIERS=1 \
|
|
266
|
+
bundle exec ruby -Itest test/application_test.rb \
|
|
267
|
+
--name test_qapplication_keyboard_modifiers_manual_ctrl_shift_smoke --verbose
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Generation Scope
|
|
271
|
+
|
|
272
|
+
Default build scope is `all`. You can still override scope manually with `QT_RUBY_SCOPE`:
|
|
273
|
+
|
|
274
|
+
- `widgets` (default): QWidget/QLayout-oriented classes.
|
|
275
|
+
- `qobject`: QObject descendants excluding QWidget/QLayout branch.
|
|
276
|
+
- `all`: combined public surface from `widgets` + `qobject` scopes (default build mode).
|
|
277
|
+
|
|
278
|
+
Examples:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
QT_RUBY_SCOPE=widgets bundle exec rake compile
|
|
282
|
+
QT_RUBY_SCOPE=qobject bundle exec rake compile
|
|
283
|
+
QT_RUBY_SCOPE=all bundle exec rake compile
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
If Qt is in a custom prefix:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
export PKG_CONFIG_PATH="/path/to/qt/lib/pkgconfig:$PKG_CONFIG_PATH"
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Native event-runtime debug logs:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
QT_RUBY_EVENT_DEBUG=1 ruby your_app.rb
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Optional tuning:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# enable ancestor fallback for MouseMove events (off by default)
|
|
302
|
+
QT_RUBY_EVENT_ANCESTOR_MOUSE_MOVE=1 ruby your_app.rb
|
|
303
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rake/clean'
|
|
4
|
+
require 'rake/testtask'
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
require 'rubocop/rake_task'
|
|
7
|
+
|
|
8
|
+
CLEAN.include(
|
|
9
|
+
'**/*.o',
|
|
10
|
+
'**/*.so',
|
|
11
|
+
'**/*.bundle',
|
|
12
|
+
'**/*.dll',
|
|
13
|
+
'**/*.dylib',
|
|
14
|
+
'ext/**/Makefile',
|
|
15
|
+
'ext/**/mkmf.log',
|
|
16
|
+
'ext/**/.sitearchdir.time',
|
|
17
|
+
'ext/**/.sitelibdir.time',
|
|
18
|
+
'tmp',
|
|
19
|
+
'build'
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
EXT_DIR = File.expand_path('ext/qt_ruby_bridge', __dir__)
|
|
23
|
+
NATIVE_BUILD_DIR = File.expand_path('build/native', __dir__)
|
|
24
|
+
GENERATOR = File.expand_path('scripts/generate_bridge.rb', __dir__)
|
|
25
|
+
GEMSPEC_PATH = File.expand_path('qt.gemspec', __dir__)
|
|
26
|
+
GEM_SPEC = Gem::Specification.load(GEMSPEC_PATH)
|
|
27
|
+
GEM_FILE = "#{GEM_SPEC.name}-#{GEM_SPEC.version}.gem".freeze
|
|
28
|
+
PKG_DIR = File.expand_path('build/pkg', __dir__)
|
|
29
|
+
PKG_FILE = File.join(PKG_DIR, GEM_FILE)
|
|
30
|
+
GENERATED_CPP_PATH = File.expand_path('build/generated/qt_ruby_bridge.cpp', __dir__)
|
|
31
|
+
NATIVE_CPP_PATH = File.join(NATIVE_BUILD_DIR, 'qt_ruby_bridge.cpp')
|
|
32
|
+
|
|
33
|
+
desc 'Generate native bridge sources from system Qt headers'
|
|
34
|
+
task :generate_bindings do
|
|
35
|
+
sh "QT_RUBY_SCOPE=all ruby #{GENERATOR}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
namespace :generate_bindings do
|
|
39
|
+
desc 'Generate bindings for widgets scope'
|
|
40
|
+
task :widgets do
|
|
41
|
+
sh "QT_RUBY_SCOPE=widgets ruby #{GENERATOR}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc 'Generate bindings for qobject scope'
|
|
45
|
+
task :qobject do
|
|
46
|
+
sh "QT_RUBY_SCOPE=qobject ruby #{GENERATOR}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc 'Generate bindings for all scope'
|
|
50
|
+
task :all do
|
|
51
|
+
sh "QT_RUBY_SCOPE=all ruby #{GENERATOR}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc 'Compile native Qt bridge'
|
|
56
|
+
task compile: 'compile:all'
|
|
57
|
+
|
|
58
|
+
def compile_for_scope(scope)
|
|
59
|
+
sh "mkdir -p #{NATIVE_BUILD_DIR}"
|
|
60
|
+
sh "cp #{GENERATED_CPP_PATH} #{NATIVE_CPP_PATH}"
|
|
61
|
+
sh({ 'QT_RUBY_SCOPE' => scope }, "ruby #{File.join(EXT_DIR, 'extconf.rb')}", chdir: NATIVE_BUILD_DIR)
|
|
62
|
+
sh 'make', chdir: NATIVE_BUILD_DIR
|
|
63
|
+
sh 'mkdir -p ../qt', chdir: NATIVE_BUILD_DIR
|
|
64
|
+
sh 'cp qt_ruby_bridge.so ../qt/qt_ruby_bridge.so', chdir: NATIVE_BUILD_DIR
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
namespace :compile do
|
|
68
|
+
%w[widgets qobject all].each do |scope|
|
|
69
|
+
desc "Compile native Qt bridge for #{scope} scope"
|
|
70
|
+
task scope.to_sym => "generate_bindings:#{scope}" do
|
|
71
|
+
compile_for_scope(scope)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
Rake::TestTask.new do |t|
|
|
77
|
+
t.libs << 'test'
|
|
78
|
+
t.pattern = 'test/**/*_test.rb'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
desc 'Build gem package'
|
|
82
|
+
task :build_gem do
|
|
83
|
+
sh "mkdir -p #{PKG_DIR}"
|
|
84
|
+
sh "gem build #{GEMSPEC_PATH} --output #{PKG_FILE}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
desc 'Install gem locally (build + gem install --local)'
|
|
88
|
+
task install: :build_gem do
|
|
89
|
+
sh "gem install --local --force #{PKG_FILE}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
RuboCop::RakeTask.new(:rubocop)
|
|
93
|
+
|
|
94
|
+
task default: :test
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __dir__))
|
|
4
|
+
require 'qt'
|
|
5
|
+
|
|
6
|
+
width = 640
|
|
7
|
+
height = 360
|
|
8
|
+
|
|
9
|
+
app = QApplication.new(0, [])
|
|
10
|
+
|
|
11
|
+
window = QWidget.new do |w|
|
|
12
|
+
w.setWindowTitle('Qt Ruby App')
|
|
13
|
+
w.resize(width, height)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
QLabel.new(window) do |l|
|
|
17
|
+
l.setText('Hello from Ruby')
|
|
18
|
+
l.setAlignment(Qt::AlignCenter)
|
|
19
|
+
l.setGeometry(0, 0, width, height)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
exit(app.exec)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __dir__))
|
|
4
|
+
require 'qt'
|
|
5
|
+
require 'irb'
|
|
6
|
+
|
|
7
|
+
app = QApplication.new(0, [])
|
|
8
|
+
window = QWidget.new do |w|
|
|
9
|
+
w.set_window_title('Qt Live Layout Console')
|
|
10
|
+
w.resize(700, 500)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
layout = QVBoxLayout.new(window)
|
|
14
|
+
window.set_layout(layout)
|
|
15
|
+
|
|
16
|
+
items = []
|
|
17
|
+
|
|
18
|
+
banner = QLabel.new(window) do |l|
|
|
19
|
+
l.set_text('Use IRB helpers: add_label, add_button, remove_last')
|
|
20
|
+
l.set_alignment(Qt::AlignCenter)
|
|
21
|
+
end
|
|
22
|
+
layout.add_widget(banner)
|
|
23
|
+
items << banner
|
|
24
|
+
|
|
25
|
+
jobs = Queue.new
|
|
26
|
+
running = true
|
|
27
|
+
|
|
28
|
+
console = Object.new
|
|
29
|
+
console.instance_variable_set(:@app, app)
|
|
30
|
+
console.instance_variable_set(:@window, window)
|
|
31
|
+
console.instance_variable_set(:@layout, layout)
|
|
32
|
+
console.instance_variable_set(:@jobs, jobs)
|
|
33
|
+
console.instance_variable_set(:@items, items)
|
|
34
|
+
|
|
35
|
+
console.define_singleton_method(:app) { @app }
|
|
36
|
+
console.define_singleton_method(:window) { @window }
|
|
37
|
+
console.define_singleton_method(:layout) { @layout }
|
|
38
|
+
console.define_singleton_method(:items) { @items }
|
|
39
|
+
|
|
40
|
+
console.define_singleton_method(:gui) do |&block|
|
|
41
|
+
raise ArgumentError, 'pass block to gui { ... }' unless block
|
|
42
|
+
|
|
43
|
+
reply = Queue.new
|
|
44
|
+
@jobs << [block, reply]
|
|
45
|
+
ok, value = reply.pop
|
|
46
|
+
raise value unless ok
|
|
47
|
+
|
|
48
|
+
value
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
console.define_singleton_method(:add_label) do |text = 'new label'|
|
|
52
|
+
gui do
|
|
53
|
+
label = QLabel.new(@window)
|
|
54
|
+
label.set_text(text)
|
|
55
|
+
label.set_alignment(Qt::AlignCenter)
|
|
56
|
+
@layout.add_widget(label)
|
|
57
|
+
@items << label
|
|
58
|
+
label
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
console.define_singleton_method(:add_button) do |text = 'new button'|
|
|
63
|
+
gui do
|
|
64
|
+
button = QPushButton.new(@window)
|
|
65
|
+
button.set_text(text)
|
|
66
|
+
@layout.add_widget(button)
|
|
67
|
+
@items << button
|
|
68
|
+
button
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
console.define_singleton_method(:remove_last) do
|
|
73
|
+
gui do
|
|
74
|
+
widget = @items.pop
|
|
75
|
+
if widget
|
|
76
|
+
@layout.remove_widget(widget)
|
|
77
|
+
widget.hide if widget.respond_to?(:hide)
|
|
78
|
+
widget
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
console.define_singleton_method(:help) do
|
|
84
|
+
puts 'Examples:'
|
|
85
|
+
puts ' add_label("Header")'
|
|
86
|
+
puts ' add_button("Run")'
|
|
87
|
+
puts ' remove_last'
|
|
88
|
+
puts ' gui { window.resize(900, 600) }'
|
|
89
|
+
puts ' items'
|
|
90
|
+
puts ' exit'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
window.show
|
|
94
|
+
QApplication.process_events
|
|
95
|
+
|
|
96
|
+
puts 'Starting IRB with live layout editor.'
|
|
97
|
+
puts 'Objects: app, window, layout, items'
|
|
98
|
+
console.help
|
|
99
|
+
|
|
100
|
+
irb_thread = Thread.new do
|
|
101
|
+
IRB.setup(nil)
|
|
102
|
+
workspace = IRB::WorkSpace.new(console.instance_eval { binding })
|
|
103
|
+
irb = IRB::Irb.new(workspace)
|
|
104
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
|
105
|
+
|
|
106
|
+
catch(:IRB_EXIT) { irb.eval_input }
|
|
107
|
+
ensure
|
|
108
|
+
jobs << :__exit__
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# TODO: Replace manual process_events polling with app.exec + QTimer-driven integration.
|
|
112
|
+
while running
|
|
113
|
+
loop do
|
|
114
|
+
job = jobs.pop(true)
|
|
115
|
+
|
|
116
|
+
if job == :__exit__
|
|
117
|
+
running = false
|
|
118
|
+
break
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
block, reply = job
|
|
122
|
+
begin
|
|
123
|
+
reply << [true, block.call]
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
reply << [false, e]
|
|
126
|
+
end
|
|
127
|
+
rescue ThreadError
|
|
128
|
+
break
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
QApplication.process_events
|
|
132
|
+
running = false if window.is_visible.zero?
|
|
133
|
+
sleep(0.01)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
irb_thread.kill if irb_thread&.alive?
|
|
137
|
+
app.dispose
|