simple_gui_creator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,542 @@
1
+ =begin
2
+ Copyright 2010, Roger Pack
3
+ This file is part of Sensible Cinema.
4
+
5
+ Sensible Cinema is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ Sensible Cinema 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
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with Sensible Cinema. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+ require 'java'
19
+ require 'sane' # gem dependency
20
+
21
+ module SimpleGuiCreator
22
+
23
+ include_package 'javax.swing'
24
+ # will use these constants (http://jira.codehaus.org/browse/JRUBY-5107)
25
+ [JProgressBar, JButton, JFrame, JLabel, JPanel, JOptionPane,
26
+ JFileChooser, JComboBox, JDialog, SwingUtilities, JSlider, JPasswordField, UIManager]
27
+
28
+ include_package 'java.awt'
29
+ [FlowLayout, Font, BorderFactory, BorderLayout, FileDialog]
30
+
31
+ java_import java.awt.event.ActionListener
32
+
33
+ JFile = java.io.File # no import for this one...
34
+
35
+ class JOptionPane
36
+ JOptionReturnValuesTranslator = {0 => :yes, 1 => :no, 2 => :cancel, -1 => :exited}
37
+
38
+ # accepts :yes => "yes text", :no => "no text"
39
+ # returns :yes :no :cancel or :exited
40
+ # you must specify text for valid options.
41
+ # example, if you specify :cancel => 'cancel' then it won't raise if they cancel
42
+ # raises if they select cancel or exit, unless you pass in :exited => true as an option
43
+ def self.show_select_buttons_prompt message, names_hash = {}
44
+ names_hash[:yes] ||= 'Yes'
45
+ names_hash[:no] ||= 'No'
46
+ # ok ?
47
+ old = ['no', 'yes', 'ok'].map{|name| 'OptionPane.' + name + 'ButtonText'}.map{|name| [name, UIManager.get(name)]}
48
+ if names_hash[:yes]
49
+ UIManager.put("OptionPane.yesButtonText", names_hash[:yes])
50
+ end
51
+ if names_hash[:no]
52
+ UIManager.put("OptionPane.noButtonText", names_hash[:no])
53
+ end
54
+ # if names_hash[:ok] # ???
55
+ # UIManager.put("OptionPane.okButtonText", names_hash[:ok])
56
+ # end
57
+ if names_hash[:cancel]
58
+ UIManager.put("OptionPane.noButtonText", names_hash[:cancel])
59
+ end
60
+ title = message.split(' ')[0..5].join(' ')
61
+ returned = JOptionPane.showConfirmDialog SwingHelpers.get_always_on_top_frame, message, title, JOptionPane::YES_NO_CANCEL_OPTION
62
+ SwingHelpers.close_always_on_top_frame
63
+ old.each{|name, old_setting| UIManager.put(name, old_setting)}
64
+ out = JOptionReturnValuesTranslator[returned]
65
+ if !out || !names_hash.key?(out)
66
+ raise 'canceled or exited an option prompt:' + out.to_s + ' ' + message
67
+ end
68
+ out
69
+ end
70
+
71
+ end
72
+
73
+ class JButton
74
+
75
+ def initialize(*args)
76
+ super(*args)
77
+ set_font Font.new("Tahoma", Font::PLAIN, 11)
78
+ end
79
+
80
+ def on_clicked &block
81
+ raise unless block # sanity check
82
+ @block = block
83
+ add_action_listener do |e|
84
+ begin
85
+ block.call
86
+ rescue Exception => e
87
+ # e.backtrace[0] == "/Users/rogerdpack/sensible-cinema/lib/gui/create.rb:149:in `setup_create_buttons'"
88
+ bt_out = ""
89
+ for line in e.backtrace[0..1]
90
+ backtrace_pieces = line.split(':')
91
+ backtrace_pieces.shift if OS.doze? # ignore drive letter
92
+ filename = backtrace_pieces[0].split('/')[-1]
93
+ line_number = backtrace_pieces[1]
94
+ bt_out += " #{filename}:#{line_number}"
95
+ end
96
+ puts 'button cancelled somehow!' + e.to_s + ' ' + get_text[0..50] + bt_out
97
+ if $VERBOSE
98
+ puts "got fatal exception thrown in button [aborted] #{e} #{e.class} #{e.backtrace[0]}"
99
+ puts e.backtrace, e
100
+ end
101
+ end
102
+ end
103
+ self
104
+ end
105
+
106
+ def simulate_click
107
+ @block.call
108
+ end
109
+
110
+ def tool_tip= text
111
+ if text
112
+ text = "<html>" + text + "</html>"
113
+ text = text.gsub("\n", "<br/>")
114
+ end
115
+ self.set_tool_tip_text text
116
+ end
117
+
118
+ def enable
119
+ set_enabled true
120
+ end
121
+
122
+ def disable
123
+ set_enabled false
124
+ end
125
+
126
+ end
127
+
128
+ ToolTipManager.sharedInstance().setDismissDelay(10000) # these are way too fast normally
129
+
130
+ class JFrame
131
+
132
+ #class StateListener
133
+ # include java.awt.event.WindowListener
134
+ #end
135
+
136
+ class CloseListener < java.awt.event.WindowAdapter
137
+ def initialize parent, &block
138
+ super()
139
+ @parent = parent
140
+ @block = block
141
+ end
142
+
143
+ def windowClosed event # sometimes this, sometimes the other...
144
+ if @block
145
+ b = @block # force avoid calling it twice, since swing does seem to call this method twice, bizarrely
146
+ @block = nil
147
+ b.call
148
+ end
149
+ end
150
+
151
+ def windowClosing event
152
+ #p 'windowClosing' # hitting the X goes *only* here, and twice? ok this is messed up
153
+ @parent.dispose
154
+ end
155
+ end
156
+
157
+ def initialize *args
158
+ super(*args) # we do always get here...
159
+ # because we do this, you should *not* have to call the unsafe:
160
+ # setDefaultCloseOperation(EXIT_ON_CLOSE)
161
+ # which basically does a System.exit(0) when the last jframe closes. Yikes jdk, yikes.
162
+ #addWindowListener(CloseListener.new(self))
163
+ dispose_on_close # don't keep running after being closed, and prevent the app from exiting! whoa!
164
+ end
165
+
166
+ def close
167
+ dispose # <sigh>
168
+ end
169
+
170
+ def dispose_on_close
171
+ setDefaultCloseOperation JFrame::DISPOSE_ON_CLOSE
172
+ end
173
+
174
+ def after_closed &block
175
+ addWindowListener(CloseListener.new(self) {
176
+ block.call
177
+ })
178
+ end
179
+
180
+ def after_minimized &block
181
+ addWindowStateListener {|e|
182
+ if e.new_state == java::awt::Frame::ICONIFIED
183
+ block.call
184
+ else
185
+ #puts 'non restore'
186
+ end
187
+ }
188
+ end
189
+
190
+ def after_restored_either_way &block
191
+ addWindowStateListener {|e|
192
+ if e.new_state == java::awt::Frame::NORMAL
193
+ block.call
194
+ else
195
+ #puts 'non restore'
196
+ end
197
+ }
198
+ end
199
+
200
+
201
+ def bring_to_front # kludgey...but said to work for swing frames...
202
+ java.awt.EventQueue.invokeLater{
203
+ unminimize
204
+ toFront
205
+ repaint
206
+ }
207
+ end
208
+
209
+ def minimize
210
+ setState(java.awt.Frame::ICONIFIED)
211
+ end
212
+
213
+ def restore
214
+ setState(java.awt.Frame::NORMAL) # this line is probably enough, but do more just in case...
215
+ #setVisible(true)
216
+ end
217
+
218
+ alias unminimize restore
219
+
220
+ def maximize
221
+ setExtendedState(JFrame::MAXIMIZED_BOTH)
222
+ end
223
+
224
+ # avoid jdk6 always on top bug http://betterlogic.com/roger/2012/04/jframe-setalwaysontop-doesnt-work-after-using-joptionpane/
225
+ alias always_on_top_original always_on_top=
226
+
227
+ def always_on_top=bool
228
+ always_on_top_original false
229
+ always_on_top_original bool
230
+ end
231
+
232
+ def set_always_on_top bool
233
+ always_on_top=bool
234
+ end
235
+
236
+ def setAlwaysOnTop bool
237
+ always_on_top=bool
238
+ end
239
+
240
+ end # class JFrame
241
+
242
+ def self.open_url_to_view_it_non_blocking url
243
+ raise 'non http url?' unless url =~ /^http/i
244
+ if OS.windows?
245
+ system("start #{url.gsub('&', '^&')}") # LODO would launchy help/work here with the full url?
246
+ else
247
+ system "#{OS.open_file_command} \"#{url}\""
248
+ sleep 2 # disallow exiting immediately after...LODO
249
+ end
250
+ end
251
+
252
+ # wrapped in sensible-cinema-base
253
+ class JFileChooser
254
+ # also set_current_directory et al...
255
+
256
+ # raises on failure...
257
+ def go show_save_dialog_instead = false
258
+ if show_save_dialog_instead
259
+ success = show_save_dialog nil
260
+ else
261
+ success = show_open_dialog nil
262
+ end
263
+ unless success == Java::javax::swing::JFileChooser::APPROVE_OPTION
264
+ java.lang.System.exit 1 # kills background proc...but we shouldn't let them do stuff while a background proc is running, anyway
265
+ end
266
+ get_selected_file.get_absolute_path
267
+ end
268
+
269
+ # match FileDialog methods...
270
+ def set_title x
271
+ set_dialog_title x
272
+ end
273
+
274
+ def set_file f
275
+ set_selected_file JFile.new(f)
276
+ end
277
+
278
+ alias setFile set_file
279
+
280
+ end
281
+
282
+
283
+ # choose a file that may or may not exist yet...
284
+ def self.new_nonexisting_or_existing_filechooser_and_go title = nil, default_dir = nil, default_filename = nil # within JFileChooser class for now...
285
+ out = JFileChooser.new
286
+ out.set_title title
287
+ if default_dir
288
+ out.set_current_directory JFile.new(default_dir)
289
+ end
290
+ if default_filename
291
+ out.set_file default_filename
292
+ end
293
+ got = out.go true
294
+ got
295
+ end
296
+
297
+ # choose a file that may or may not exist yet...
298
+ def self.new_nonexisting_filechooser_and_go title = nil, default_dir = nil, default_filename = nil # within JFileChooser class for now...
299
+ out = JFileChooser.new
300
+ out.set_title title
301
+ if default_dir
302
+ out.set_current_directory JFile.new(default_dir)
303
+ end
304
+ if default_filename
305
+ out.set_file default_filename
306
+ end
307
+ found_non_exist = false
308
+ while(!found_non_exist)
309
+ got = out.go true
310
+ if(File.exist? got)
311
+ SwingHelpers.show_blocking_message_dialog 'this file already exists, choose a new filename ' + got
312
+ else
313
+ found_non_exist = true
314
+ end
315
+ end
316
+ got
317
+ end
318
+
319
+ def self.new_existing_dir_chooser_and_go title=nil, default_dir = nil
320
+ chooser = JFileChooser.new;
321
+ chooser.setCurrentDirectory(JFile.new default_dir) if default_dir
322
+ chooser.set_title title
323
+ chooser.setFileSelectionMode(JFileChooser::DIRECTORIES_ONLY)
324
+ chooser.setAcceptAllFileFilterUsed(false)
325
+ chooser.set_approve_button_text "Select This Directory"
326
+
327
+ if (chooser.showOpenDialog(nil) == JFileChooser::APPROVE_OPTION)
328
+ return chooser.getSelectedFile().get_absolute_path
329
+ else
330
+ raise "No dir selected " + title.to_s
331
+ end
332
+ end
333
+
334
+ # awt...the native looking one...
335
+ class FileDialog
336
+ def go
337
+ show
338
+ dispose # allow app to exit :P
339
+ if get_file
340
+ # they picked something...
341
+ File.expand_path(get_directory + '/' + get_file)
342
+ else
343
+ nil
344
+ end
345
+ end
346
+ end
347
+
348
+ # this doesn't have an "awesome" way to force existence, just loops
349
+ def self.new_previously_existing_file_selector_and_go title, use_this_dir = nil
350
+
351
+ out = FileDialog.new(nil, title, FileDialog::LOAD) # LODO no self in here... ?
352
+ out.set_title title
353
+ out.set_filename_filter {|file, name|
354
+ true # works os x, not doze?
355
+ }
356
+
357
+ if use_this_dir
358
+ # FileDialog only accepts paths a certain way...
359
+ dir = File.expand_path(use_this_dir)
360
+ dir = dir.gsub(File::Separator, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
361
+ out.setDirectory(dir)
362
+ end
363
+ got = nil
364
+ while(!got)
365
+ got = out.go
366
+ raise 'cancelled choosing existing file ' + title unless got # I think we always want to raise...
367
+ unless File.exist? got
368
+ show_blocking_message_dialog "please select a file that already exists, or cancel"
369
+ got = nil
370
+ end
371
+ end
372
+ got
373
+ end
374
+
375
+ class NonBlockingDialog < JDialog
376
+ def initialize title_and_display_text, close_button_text = 'Close'
377
+ super nil # so that set_title will work
378
+ lines = title_and_display_text.split("\n")
379
+ set_title lines[0]
380
+ get_content_pane.set_layout nil
381
+ lines.each_with_index{|line, idx|
382
+ jlabel = JLabel.new line
383
+ jlabel.set_bounds(10, 15*idx, 550, 24)
384
+ get_content_pane.add jlabel
385
+ }
386
+ close = JButton.new( close_button_text ).on_clicked {
387
+ self.dispose
388
+ }
389
+ number_of_lines = lines.length
390
+ close.set_bounds(125,30+15*number_of_lines, close_button_text.length * 15,25)
391
+ get_content_pane.add close
392
+ set_size 550, 100+(15*number_of_lines) # XXX variable width? or use swing build in better?
393
+ set_visible true
394
+ setDefaultCloseOperation JFrame::DISPOSE_ON_CLOSE
395
+ setLocationRelativeTo nil # center it on the screen
396
+
397
+ end
398
+ alias close dispose # yikes
399
+ end
400
+
401
+ def self.get_always_on_top_frame
402
+ fake_frame = JFrame.new
403
+ fake_frame.setUndecorated true # so we can have a teeny [invisible] window
404
+ fake_frame.set_size 1,1
405
+ fake_frame.set_location 300,300 # so that option pane's won't appear upper left
406
+ #fake_frame.always_on_top = true
407
+ fake_frame.show
408
+ @fake_frame = fake_frame
409
+ end
410
+
411
+ def self.close_always_on_top_frame
412
+ @fake_frame.close
413
+ end
414
+
415
+ # prompts for user input, raises if they cancel the prompt or if they enter nothing
416
+ def self.get_user_input(message, default = '', cancel_or_blank_ok = false)
417
+ p 'please enter the information in the prompt:' + message[0..50] + '...'
418
+ received = javax.swing.JOptionPane.showInputDialog(get_always_on_top_frame, message, default)
419
+ close_always_on_top_frame
420
+ if !cancel_or_blank_ok
421
+ raise 'user cancelled input prompt ' + message unless received
422
+ raise 'did not enter anything?' + message unless received.present?
423
+ received = nil # always return nil
424
+ end
425
+ p 'received answer:' + received
426
+ received
427
+ end
428
+
429
+ def self.show_in_explorer filename_or_path
430
+ raise 'nonexistent file cannot reveal in explorer?' + filename_or_path unless File.exist?(filename_or_path)
431
+ if OS.doze?
432
+ begin
433
+ c = "explorer /e,/select,#{filename_or_path.to_filename}"
434
+ system c # command returns immediately...so system is ok
435
+ rescue => why_does_this_happen_ignore_this_exception_it_probably_actually_succeeded
436
+ end
437
+ elsif OS.mac?
438
+ c = "open -R " + "\"" + filename_or_path.to_filename + "\""
439
+ puts c
440
+ system c
441
+ else
442
+ raise 'os reveal unsupported?'
443
+ end
444
+ end
445
+
446
+ def self.show_blocking_message_dialog message, title = message.split("\n")[0], style= JOptionPane::INFORMATION_MESSAGE
447
+ puts "please use GUI window popup... #{message} ..."
448
+ JOptionPane.showMessageDialog(get_always_on_top_frame, message, title, style)
449
+ # the above has no return value <sigh> so just return true
450
+ close_always_on_top_frame
451
+ true
452
+ end
453
+
454
+ class << self
455
+ alias :show_message :show_blocking_message_dialog
456
+ end
457
+
458
+ def self.show_non_blocking_message_dialog message, close_button_text = 'Close'
459
+ NonBlockingDialog.new(message, close_button_text) # we don't care if they close this one via the x
460
+ end
461
+
462
+ def self.show_select_buttons_prompt message, names_hash = {}
463
+ JOptionPane.show_select_buttons_prompt message, names_hash
464
+ end
465
+
466
+ def self.get_password_input text
467
+ p 'please enter password at prompt'
468
+ pwd = JPasswordField.new(10)
469
+ got = JOptionPane.showConfirmDialog(get_always_on_top_frame, pwd, text, JOptionPane::OK_CANCEL_OPTION) < 0
470
+ close_always_on_top_frame
471
+ if got
472
+ raise 'cancelled ' + text
473
+ else
474
+ # convert to ruby string [?]
475
+ out = ''
476
+ pwd.password.each{|b|
477
+ out << b
478
+ }
479
+ out
480
+ end
481
+ end
482
+
483
+ class DropDownSelector < JDialog # JDialog is blocking...
484
+
485
+ def initialize parent, options_array, prompt_for_top_entry
486
+ super parent, true
487
+ set_title prompt_for_top_entry
488
+ @drop_down_elements = options_array
489
+ @selected_idx = nil
490
+ box = JComboBox.new
491
+ box.add_action_listener do |e|
492
+ idx = box.get_selected_index
493
+ if idx != 0
494
+ # don't count choosing the first as a real entry
495
+ @selected_idx = box.get_selected_index - 1
496
+ dispose
497
+ end
498
+ end
499
+
500
+ box.add_item @prompt = prompt_for_top_entry # put something in index 0
501
+ options_array.each{|drive|
502
+ box.add_item drive
503
+ }
504
+ add box
505
+ pack # how do you get this arbitrary size? what the...
506
+
507
+ end
508
+
509
+ # returns index from initial array that they selected, or raises if they hit the x on it
510
+ def go_selected_index
511
+ puts 'select from dropdown window'
512
+ show # blocks...
513
+ raise 'did not select, exited early ' + @prompt unless @selected_idx
514
+ @selected_idx
515
+ end
516
+
517
+ def go_selected_value
518
+ puts 'select from dropdown window ' + @drop_down_elements[-1] + ' ...'
519
+ show # blocks...
520
+ raise 'did not select, exited early ' + @prompt unless @selected_idx
521
+ @drop_down_elements[@selected_idx]
522
+ end
523
+
524
+ end
525
+
526
+ def self.hard_exit!; java::lang::System.exit 0; end
527
+
528
+ def self.invoke_in_gui_thread # sometimes I think I need this, but it never seems to help
529
+ SwingUtilities.invoke_later { yield }
530
+ end
531
+
532
+ end
533
+
534
+ class String
535
+ def to_filename
536
+ if File::ALT_SEPARATOR
537
+ File.expand_path(self).gsub('/', File::ALT_SEPARATOR)
538
+ else
539
+ File.expand_path(self)
540
+ end
541
+ end
542
+ end
@@ -0,0 +1,124 @@
1
+ =begin
2
+ Copyright 2010, Roger Pack
3
+ This file is part of Sensible Cinema.
4
+
5
+ Sensible Cinema is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ Sensible Cinema 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
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with Sensible Cinema. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+
19
+ # shamelessly stolen from redcar
20
+
21
+ require 'yaml'
22
+ class Storage
23
+ class << self
24
+ attr_writer :storage_dir
25
+ end
26
+
27
+ def self.storage_dir
28
+ @user_dir ||= File.join(File.expand_path('~'), ".sensible_cinema_storage")
29
+ end
30
+
31
+ # Open a storage file or create it if it doesn't exist.
32
+ #
33
+ # @param [String] a (short) name, should be suitable for use as a filename
34
+ def initialize(name)
35
+ @name = name
36
+ unless File.exists?(Storage.storage_dir)
37
+ require 'fileutils'
38
+ FileUtils.mkdir_p(Storage.storage_dir)
39
+ end
40
+ rollback
41
+ end
42
+
43
+ # Save the storage to disk.
44
+ def save
45
+ File.open(path, "w") { |f| YAML.dump(@storage, f) }
46
+ update_timestamp
47
+ self
48
+ end
49
+
50
+ def clear!
51
+ @storage = {}
52
+ begin
53
+ File.delete path
54
+ rescue => e
55
+ if File.exist? path
56
+ raise
57
+ end
58
+ end
59
+ end
60
+
61
+ # Rollback the storage to the latest revision saved to disk or empty it if
62
+ # it hasn't been saved.
63
+ def rollback
64
+ if File.exists?(path)
65
+ @storage = YAML.load_file(path)
66
+ unless @storage.is_a? Hash
67
+
68
+ $stderr.puts 'storage file is corrupted--deleting ' + path
69
+ clear!
70
+
71
+ end
72
+ update_timestamp
73
+ else
74
+ @storage = {}
75
+ end
76
+ self
77
+ end
78
+
79
+ # retrieve key value
80
+ # note: it does not re-read from disk before returning you this value
81
+ def [](key)
82
+ if @last_modified_time
83
+ if File.exist?(path()) && (File.stat(path()).mtime != @last_modified_time)
84
+ rollback
85
+ end
86
+ end
87
+ @storage[key]
88
+ end
89
+
90
+ # set key to value
91
+ # note: it automatically saves this to disk
92
+ def []=(key, value)
93
+ @storage[key] = value
94
+ save
95
+ value
96
+ end
97
+
98
+ def set_default(key, value)
99
+ unless @storage.has_key?(key)
100
+ self[key] = value
101
+ end
102
+ value
103
+ end
104
+
105
+ def set_once key
106
+ unless @storage.has_key?(key)
107
+ self[key] = yield
108
+ end
109
+ end
110
+
111
+ def keys
112
+ @storage.keys
113
+ end
114
+
115
+ private
116
+
117
+ def path
118
+ File.join(Storage.storage_dir, @name + ".yaml")
119
+ end
120
+
121
+ def update_timestamp
122
+ @last_modified_time = File.stat(path()).mtime
123
+ end
124
+ end
@@ -0,0 +1,18 @@
1
+ # just autoload everything always :)
2
+
3
+ module SimpleGuiBootStrap
4
+ def self.snake_case string
5
+ string = string.to_s.dup
6
+ string.gsub!(/::/, '/')
7
+ string.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
8
+ string.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
9
+ string.tr!("-", "_")
10
+ string.downcase!
11
+ string
12
+ end
13
+ end
14
+
15
+ for clazz in [:DriveInfo, :MouseControl, :ParseTemplate, :PlayAudio, :PlayMp3Audio, :RubyClip, :SimpleGuiCreator]
16
+ new_path = File.dirname(__FILE__) + '/simple_gui_creator/' + SimpleGuiBootStrap.snake_case(clazz) + '.rb'
17
+ autoload clazz, new_path
18
+ end
data/spec/common.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'rspec/autorun'
3
+ require 'sane'
4
+ #$: << File.dirname(__FILE__) + '/../lib'
5
+ require_relative '../lib/simple_gui_creator'
data/spec/diesel.mp3 ADDED
Binary file