simple_gui_creator 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.
@@ -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