gtk4 4.2.2 → 4.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,323 @@
1
+ # Copyright (C) 2024 Ruby-GNOME Project Team
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License as published by the Free Software Foundation; either
6
+ # version 2.1 of the License, or (at your option) any later version.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+
17
+ # Original:
18
+ # https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/gtk-demo/glarea.c
19
+ #
20
+ # See the following for license information:
21
+ # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/AUTHORS
22
+ # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/COPYING
23
+
24
+ require "opengl"
25
+
26
+ class GtkGLAreaDemo < GtkDemo
27
+ X_AXIS = 0
28
+ Y_AXIS = 1
29
+ Z_AXIS = 2
30
+ N_AXISES = 3
31
+
32
+ def initialize
33
+ super(__FILE__,
34
+ "OpenGL/OpenGL Area",
35
+ "Gtk::GLArea is a widget that allows custom drawing using OpenGL calls.",
36
+ [])
37
+ end
38
+
39
+ def run(parent)
40
+ window = Gtk::Window.new
41
+ window.display = parent.display
42
+ window.title = "OpenGL Area"
43
+ window.set_default_size(400, 600)
44
+
45
+ box = Gtk::Box.new(:vertical)
46
+ box.margin_start = 12
47
+ box.margin_end = 12
48
+ box.margin_top = 12
49
+ box.margin_bottom = 12
50
+ box.spacing = 6
51
+ window.child = box
52
+
53
+ gl_area = Gtk::GLArea.new
54
+ gl_area.hexpand = true
55
+ gl_area.vexpand = true
56
+ gl_area.set_size_request(100, 200)
57
+ box.append(gl_area)
58
+
59
+ # We need to initialize and free GL resources, so we use the
60
+ # realize and unrealize signals on the widget
61
+ gl_area.signal_connect("realize") do
62
+ realize(gl_area)
63
+ end
64
+ gl_area.signal_connect("unrealize") do
65
+ unrealize(gl_area)
66
+ end
67
+
68
+ # The main "draw" call for GtkGLArea
69
+ gl_area.signal_connect("render") do
70
+ render(gl_area)
71
+ end
72
+
73
+ controls = Gtk::Box.new(:vertical)
74
+ controls.hexpand = true
75
+ box.append(controls)
76
+
77
+ N_AXISES.times do |i|
78
+ controls.append(create_axis_slider(gl_area, i))
79
+ end
80
+
81
+ button = Gtk::Button.new(label: "Quit")
82
+ button.hexpand = true
83
+ box.append(button)
84
+
85
+ button.signal_connect("clicked") do
86
+ window.destroy
87
+ end
88
+
89
+ window.present
90
+
91
+ window
92
+ end
93
+
94
+ private
95
+ # We need to set up our state when we realize the GtkGLArea widget
96
+ def realize(gl_area)
97
+ @rotation_angles = [0.0] * N_AXISES
98
+
99
+ GL.load_lib
100
+
101
+ gl_area.make_current
102
+ api_name = gl_area.api.nick
103
+ vertex_path = "/glarea/glarea-#{api_name}.vs.glsl"
104
+ fragment_path = "/glarea/glarea-#{api_name}.fs.glsl"
105
+ init_buffers
106
+ init_shaders(vertex_path, fragment_path)
107
+ end
108
+
109
+ # The object we are drawing
110
+ VERTEX_DATA = [
111
+ 0.0, 0.5, 0.0, 1.0,
112
+ 0.5, -0.366, 0.0, 1.0,
113
+ -0.5, -0.366, 0.0, 1.0,
114
+ ]
115
+
116
+ def init_buffers
117
+ # We only use one VAO, so we always keep it bound
118
+ vao_buffer = " " * Fiddle::SIZEOF_UINT32_T
119
+ GL.GenVertexArrays(1, vao_buffer)
120
+ @vao = vao_buffer.unpack1("L")
121
+ GL.BindVertexArray(@vao)
122
+
123
+ # This is the buffer that holds the vertices
124
+ position_buffer = " " * Fiddle::SIZEOF_UINT32_T
125
+ GL.GenBuffers(1, position_buffer)
126
+ GL.GenBuffers(1, position_buffer)
127
+ @position = position_buffer.unpack1("L")
128
+ GL.BindBuffer(GL::ARRAY_BUFFER, @position)
129
+ GL.BufferData(GL::ARRAY_BUFFER,
130
+ Fiddle::SIZEOF_FLOAT * VERTEX_DATA.size,
131
+ VERTEX_DATA.pack("f*"),
132
+ GL::STATIC_DRAW)
133
+ GL.BindBuffer(GL::ARRAY_BUFFER, 0)
134
+ end
135
+
136
+ # Initialize the shaders and link them into a program
137
+ def init_shaders(vertex_path, fragment_path)
138
+ @program = nil
139
+ vertex = 0
140
+ fragment = 0
141
+
142
+ begin
143
+ source = Gio::Resources.lookup_data(vertex_path)
144
+ vertex = create_shader(GL::VERTEX_SHADER, source.to_s)
145
+ return if vertex.zero?
146
+
147
+ source = Gio::Resources.lookup_data(fragment_path)
148
+ fragment = create_shader(GL::FRAGMENT_SHADER, source.to_s)
149
+ return if fragment.zero?
150
+
151
+ @program = GL.CreateProgram
152
+ return if @program.zero?
153
+
154
+ GL.AttachShader(@program, vertex)
155
+ GL.AttachShader(@program, fragment)
156
+
157
+ GL.LinkProgram(@program)
158
+
159
+ program_ok_buffer = " " * Fiddle::SIZEOF_UINT32_T
160
+ GL.GetProgramiv(@program, GL::LINK_STATUS, program_ok_buffer)
161
+ program_ok = (program_ok_buffer.unpack1("L") == GL::TRUE)
162
+ unless program_ok
163
+ log_length_buffer = " " * Fiddle::SIZEOF_UINT32_T
164
+ GL.GetProgramiv(@program, GL::INFO_LOG_LENGTH, log_length_buffer)
165
+ log_length = log_length_buffer.unpack1("L")
166
+ log = " " * log_length
167
+ GL.GetProgramInfoLog(@program, log_length, nil, log)
168
+ $stderr.puts("Linking failure:")
169
+ $stderr.puts(log)
170
+ GL.DeleteProgram(@program)
171
+ @program = nil
172
+ return
173
+ end
174
+
175
+ # Get the location of the "mvp" uniform
176
+ @mvp_location = GL.GetUniformLocation(@program, "mvp")
177
+
178
+ GL.DetachShader(@program, vertex)
179
+ GL.DetachShader(@program, fragment)
180
+ ensure
181
+ GL.DeleteShader(vertex) if vertex.zero?
182
+ GL.DeleteShader(fragment) if fragment.zero?
183
+ end
184
+ end
185
+
186
+ # Create and compile a shader
187
+ def create_shader(type, source)
188
+ shader = GL.CreateShader(type)
189
+ GL.ShaderSource(shader, 1, [source].pack("p"), nil)
190
+ GL.CompileShader(shader)
191
+
192
+ shader_ok_buffer = " " * Fiddle::SIZEOF_UINT32_T
193
+ GL.GetShaderiv(shader, GL::COMPILE_STATUS, shader_ok_buffer)
194
+ shader_ok = (shader_ok_buffer.unpack1("L") == GL::TRUE)
195
+ unless shader_ok
196
+ log_length_buffer = " " * Fiddle::SIZEOF_UINT32_t
197
+ GL.GetShaderiv(shader, GL::INFO_LOG_LENGTH, log_length_buffer)
198
+ log_length = log_length_buffer.unpack1("L")
199
+ log = " " * log_length
200
+ GL.GetShaderInfoLog(shader, log_length, nil, buffer)
201
+ type_name = (type == GL::VERTEX_SHADER ? "vertex" : "fragment")
202
+ $stderr.puts("Compile failure in #{type_name} shader:")
203
+ $stderr.puts(log)
204
+ GL.DeleteShader(shader)
205
+ return 0
206
+ end
207
+
208
+ shader
209
+ end
210
+
211
+ # We should tear down the state when unrealizing
212
+ def unrealize(gl_area)
213
+ gl_area.make_current
214
+ GL.DeleteBuffers(1, [[@position].pack("L")].pack("p"))
215
+ GL.DeleteProgram(@program)
216
+ end
217
+
218
+ def compute_mvp(phi, theta, psi)
219
+ x = phi * (Math::PI / 180.0)
220
+ y = theta * (Math::PI / 180.0)
221
+ z = psi * (Math::PI / 180.0)
222
+ c1 = Math.cos(x)
223
+ s1 = Math.sin(x)
224
+ c2 = Math.cos(y)
225
+ s2 = Math.sin(y)
226
+ c3 = Math.cos(z)
227
+ s3 = Math.sin(z)
228
+ c3c2 = c3 * c2
229
+ s3c1 = s3 * c1
230
+ c3s2s1 = c3 * s2 * s1
231
+ s3s1 = s3 * s1
232
+ c3s2c1 = c3 * s2 * c1
233
+ s3c2 = s3 * c2
234
+ c3c1 = c3 * c1
235
+ s3s2s1 = s3 * s2 * s1
236
+ c3s1 = c3 * s1
237
+ s3s2c1 = s3 * s2 * c1
238
+ c2s1 = c2 * s1
239
+ c2c1 = c2 * c1
240
+
241
+ # apply all three rotations using the three matrices:
242
+ #
243
+ # ⎡ c3 s3 0 ⎤ ⎡ c2 0 -s2 ⎤ ⎡ 1 0 0 ⎤
244
+ # ⎢ -s3 c3 0 ⎥ ⎢ 0 1 0 ⎥ ⎢ 0 c1 s1 ⎥
245
+ # ⎣ 0 0 1 ⎦ ⎣ s2 0 c2 ⎦ ⎣ 0 -s1 c1 ⎦
246
+ mvp = [0.0] * 16
247
+ mvp[0] = c3c2; mvp[4] = s3c1 + c3s2s1; mvp[8] = s3s1 - c3s2c1; mvp[12] = 0.0;
248
+ mvp[1] = -s3c2; mvp[5] = c3c1 - s3s2s1; mvp[9] = c3s1 + s3s2c1; mvp[13] = 0.0;
249
+ mvp[2] = s2; mvp[6] = -c2s1; mvp[10] = c2c1; mvp[14] = 0.0;
250
+ mvp[3] = 0.0; mvp[7] = 0.0; mvp[11] = 0.0; mvp[15] = 1.0;
251
+ mvp
252
+ end
253
+
254
+ def draw_triangle
255
+ # Compute the model view projection matrix using the
256
+ # rotation angles specified through the GtkRange widgets
257
+ mvp = compute_mvp(@rotation_angles[X_AXIS],
258
+ @rotation_angles[Y_AXIS],
259
+ @rotation_angles[Z_AXIS])
260
+
261
+ # Use our shaders
262
+ GL.UseProgram(@program)
263
+
264
+ # Update the "mvp" matrix we use in the shader
265
+ GL.UniformMatrix4fv(@mvp_location, 1, GL::FALSE, mvp.pack("f*"))
266
+
267
+ # Use the vertices in our buffer
268
+ GL.BindBuffer(GL::ARRAY_BUFFER, @position)
269
+ GL.EnableVertexAttribArray(0)
270
+ GL.VertexAttribPointer(0, 4, GL::FLOAT, GL::FALSE, 0, 0)
271
+
272
+ # Draw the three vertices as a triangle
273
+ GL.DrawArrays(GL::TRIANGLES, 0, 3)
274
+
275
+ # We finished using the buffers and program
276
+ GL.DisableVertexAttribArray(0)
277
+ GL.BindBuffer(GL::ARRAY_BUFFER, 0)
278
+ GL.UseProgram(0)
279
+ end
280
+
281
+ def render(gl_area)
282
+ # Clear the viewport
283
+ GL.ClearColor(0.5, 0.5, 0.5, 1.0)
284
+ GL.Clear(GL::COLOR_BUFFER_BIT)
285
+
286
+ # Draw our object
287
+ draw_triangle
288
+
289
+ # Flush the contents of the pipeline
290
+ GL.Flush
291
+
292
+ true
293
+ end
294
+
295
+ def create_axis_slider(gl_area, axis)
296
+ box = Gtk::Box.new(:horizontal)
297
+ case axis
298
+ when X_AXIS
299
+ text = "X axis"
300
+ when Y_AXIS
301
+ text = "Y axis"
302
+ when Z_AXIS
303
+ text = "Z axis"
304
+ end
305
+ label = Gtk::Label.new(text)
306
+ box.append(label)
307
+
308
+ adjustment = Gtk::Adjustment.new(0.0, 0.0, 360.0, 1.0, 12.0, 0.0)
309
+ adjustment.signal_connect("value-changed") do
310
+ @rotation_angles[axis] = adjustment.value
311
+ # Update the contents of the GL drawing area
312
+ gl_area.queue_draw
313
+ end
314
+
315
+ slider = Gtk::Scale.new(:horizontal, adjustment)
316
+ box.append(slider)
317
+ slider.hexpand = true
318
+
319
+ box
320
+ end
321
+
322
+ GtkDemo.register(new)
323
+ end
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <interface>
3
+ <template class="GtkListItem">
4
+ <property name="focusable">0</property>
5
+ <property name="child">
6
+ <object class="GtkTreeExpander" id="expander">
7
+ <binding name="list-row">
8
+ <lookup name="item">GtkListItem</lookup>
9
+ </binding>
10
+ <property name="child">
11
+ <object class="GtkInscription">
12
+ <property name="hexpand">1</property>
13
+ <property name="nat-chars">25</property>
14
+ <property name="text-overflow">ellipsize-end</property>
15
+ <binding name="text">
16
+ <lookup name="title" type="GtkDemo">
17
+ <lookup name="item">expander</lookup>
18
+ </lookup>
19
+ </binding>
20
+ </object>
21
+ </property>
22
+ </object>
23
+ </property>
24
+ </template>
25
+ </interface>
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2024 Ruby-GNOME Project Team
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ # Original:
20
+ # https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/gtk-demo/main.c
21
+ #
22
+ # See the following for license information:
23
+ # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/AUTHORS
24
+ # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/COPYING
25
+
26
+ require "gtk4"
27
+
28
+ gresource = File.join(__dir__, "demo.gresource")
29
+ gresource_xml = "#{gresource}.xml"
30
+ need_generate = false
31
+ if File.exist?(gresource)
32
+ need_generate = File.mtime(gresource_xml) > File.mtime(gresource)
33
+ else
34
+ need_generate = true
35
+ end
36
+ if need_generate
37
+ unless system("glib-compile-resources",
38
+ "--target", gresource,
39
+ "--sourcedir", __dir__,
40
+ gresource_xml)
41
+ $stderr.puts("Failed to compile resource")
42
+ exit(false)
43
+ end
44
+ end
45
+ resource = Gio::Resource.load(gresource)
46
+ Gio::Resources.register(resource)
47
+
48
+
49
+ require_relative "demo"
50
+ Dir.glob("#{__dir__}/*.rb") do |rb|
51
+ base_name = File.basename(rb, ".rb")
52
+ next if base_name == "main"
53
+ require_relative base_name
54
+ end
55
+
56
+
57
+ def run_demo(demo, window)
58
+ demo_window = demo.run(window)
59
+ demo_window.transient_for = window
60
+ demo_window.modal = true
61
+ demo_window
62
+ end
63
+
64
+ def create_demo_model(window)
65
+ store = Gio::ListStore.new(GtkDemo)
66
+ GtkDemo.all.each do |demo|
67
+ store.append(demo)
68
+ end
69
+ store
70
+ end
71
+
72
+ def load_file(demo, notebook, info_view)
73
+ notebook.n_pages.downto(1) do |i|
74
+ notebook.remove_page(i)
75
+ end
76
+
77
+ info_buffer = Gtk::TextBuffer.new
78
+ info_buffer.create_tag("title",
79
+ size: 18 * Pango::SCALE,
80
+ pixels_below_lines: 10)
81
+ info_buffer.begin_irreversible_action do
82
+ info_buffer_iter = info_buffer.get_iter_at(offset: 0)
83
+ info_buffer.insert(info_buffer_iter, demo.title, tags: ["title"])
84
+ info_buffer.insert(info_buffer_iter, "\n")
85
+ info_buffer.insert(info_buffer_iter, demo.description)
86
+ end
87
+ info_view.buffer = info_buffer
88
+ end
89
+
90
+ def select_item(selection, window, notebook, info_view)
91
+ row = selection.selected_item
92
+ notebook.sensitive = !!row
93
+ if row
94
+ demo = row.item
95
+ load_file(demo, notebook, info_view)
96
+ run_action = window.lookup_action("run")
97
+ run_action.enabled = true
98
+ window.title = demo.title
99
+ else
100
+ window.title = "No match"
101
+ end
102
+ end
103
+
104
+
105
+ app = Gtk::Application.new("com.github.ruby-gnome.gtk4.Demo",
106
+ [:non_unique, :handles_command_line])
107
+
108
+ about_action = Gio::SimpleAction.new("about")
109
+ about_action.signal_connect("activate") do
110
+ dialog = Gtk::AboutDialog.new
111
+ dialog.modal = true
112
+ dialog.transient_for = app.active_window
113
+ dialog.destroy_with_parent = true
114
+ dialog.program_name = "Ruby/GTK4 Demo"
115
+ dialog.version = GLib::BINDING_VERSION.join(".")
116
+ dialog.copyright = "(C) 2024-#{Time.now.year} Ruby-GNOME Project Team"
117
+ dialog.license_type = Gtk::License::LGPL_2_1
118
+ dialog.website = "https://ruby-gnome.github.io/"
119
+ dialog.comments = "Program to demonstrate Ruby/GTK4 widgets"
120
+ dialog.authors = ["Ruby-GNOME Project Team"]
121
+ dialog.logo_icon_name = "org.gtk.Demo4"
122
+ dialog.title = "About Ruby/GTK4 Demo"
123
+ dialog.system_information = <<-INFO.chomp
124
+ OS\t#{GLib.get_os_info(:name)} #{GLib.get_os_info(:version)}
125
+
126
+ System Libraries
127
+ \tGLib\t#{GLib::Version::STRING}
128
+ \tPango\t#{Pango::Version::STRING}
129
+ \tGTK\t#{Gtk::Version::STRING}
130
+
131
+ A link can appear here: <https://ruby-gnome.github.io/>
132
+ INFO
133
+ dialog.present
134
+ end
135
+ app.add_action(about_action)
136
+
137
+ quit_action = Gio::SimpleAction.new("quit")
138
+ quit_action.signal_connect("activate") do
139
+ app.windows.each do |window|
140
+ window.destroy
141
+ end
142
+ end
143
+ app.add_action(quit_action)
144
+
145
+ inspector_action = Gio::SimpleAction.new("inspector")
146
+ inspector_action.signal_connect("activate") do
147
+ Gtk::Window.interactive_debugging = true
148
+ end
149
+ app.add_action(inspector_action)
150
+ app.set_accels_for_action("app.about", ["F1"])
151
+ app.set_accels_for_action("app.quit", ["<Control>q"])
152
+
153
+ app.add_main_option("version",
154
+ 0,
155
+ GLib::OptionFlags::NONE,
156
+ GLib::OptionArg::NONE,
157
+ "Show program version")
158
+ app.add_main_option("run",
159
+ 0,
160
+ GLib::OptionFlags::NONE,
161
+ GLib::OptionArg::STRING,
162
+ "Run an example",
163
+ "EXAMPLE")
164
+ app.add_main_option("list",
165
+ 0,
166
+ GLib::OptionFlags::NONE,
167
+ GLib::OptionArg::NONE,
168
+ "List examples")
169
+ app.add_main_option("auto-quit",
170
+ 0,
171
+ GLib::OptionFlags::NONE,
172
+ GLib::OptionArg::NONE,
173
+ "Quit after a delay")
174
+ run = nil
175
+ list = false
176
+ auto_quit = false
177
+ app.signal_connect("command-line") do |_app, command_line|
178
+ run = command_line.options_dict.lookup("run", "s")
179
+ list = command_line.options_dict.lookup("list", "b")
180
+ auto_quit = command_line.options_dict.lookup("auto-quit", "b")
181
+ app.activate
182
+ 0
183
+ end
184
+
185
+ app.signal_connect("handle-local-options") do |_app, options|
186
+ if options.lookup("version", "b")
187
+ puts("ruby-gtk4-demo: #{GLib::BINDING_VERSION.join(".")}")
188
+ 0
189
+ else
190
+ -1
191
+ end
192
+ end
193
+
194
+ app.signal_connect("activate") do |_app|
195
+ builder = Gtk::Builder.new(resource: "/ui/main.ui")
196
+ window = builder["window"]
197
+ app.add_window(window)
198
+
199
+ run_action = Gio::SimpleAction.new("run")
200
+ run_action.signal_connect("activate") do
201
+ row = selection.selected_item
202
+ demo = row.item
203
+ run_demo(demo, window)
204
+ end
205
+ window.add_action(run_action)
206
+
207
+ notebook = builder["notebook"]
208
+
209
+ info_view = builder["info-textview"]
210
+ source_view = builder["source-textview"]
211
+ list_view = builder["listview"]
212
+ list_view.signal_connect("activate") do |_, position|
213
+ model = list_view.model
214
+ row = model.get_item(position)
215
+ run_demo(row.item, window)
216
+ end
217
+ search_bar = builder["searchbar"]
218
+ search_bar.signal_connect("notify::search-mode-enabled") do
219
+ unless search_bar.search_mode?
220
+ bar.child.text = ""
221
+ end
222
+ end
223
+
224
+ list_model = create_demo_model(window)
225
+ tree_model = Gtk::TreeListModel.new(list_model, false, true) do |item|
226
+ item.children_model
227
+ end
228
+ filter_model = Gtk::FilterListModel.new(tree_model, nil)
229
+ filter = Gtk::CustomFilter.new do
230
+ # TODO
231
+ true
232
+ end
233
+ filter_model.filter = filter
234
+
235
+ search_entry = builder["search-entry"]
236
+
237
+ selection = Gtk::SingleSelection.new(filter_model)
238
+ selection.signal_connect("notify::selected-item") do
239
+ select_item(selection, window, notebook, info_view)
240
+ end
241
+ list_view.model = selection
242
+ select_item(selection, window, notebook, info_view)
243
+
244
+ target_demo = nil
245
+ if run
246
+ GtkDemo.each do |demo|
247
+ if demo.name == run
248
+ target_demo = demo
249
+ break
250
+ end
251
+ end
252
+ end
253
+ if target_demo
254
+ demo_window = run_demo(target_demo, window)
255
+ # TODO: This doesn't work
256
+ demo_window.signal_connect("destroy") do
257
+ app.quit
258
+ end
259
+ else
260
+ window.present
261
+ end
262
+ end
263
+
264
+ app.run([$0] + ARGV)