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.
- data/README +70 -0
- data/Rakefile +13 -0
- data/TODO +4 -0
- data/VERSION +1 -0
- data/bin/simple_gui_creator +88 -0
- data/examples/absolute_positioning.rb +12 -0
- data/ext/jl1.0.1.jar +0 -0
- data/lib/simple_gui_creator/drive_info.rb +162 -0
- data/lib/simple_gui_creator/mouse_control.rb +149 -0
- data/lib/simple_gui_creator/parse_template.rb +207 -0
- data/lib/simple_gui_creator/play_audio.rb +79 -0
- data/lib/simple_gui_creator/play_mp3_audio.rb +42 -0
- data/lib/simple_gui_creator/ruby_clip.rb +33 -0
- data/lib/simple_gui_creator/simple_gui_creator.rb +542 -0
- data/lib/simple_gui_creator/storage.rb +124 -0
- data/lib/simple_gui_creator.rb +18 -0
- data/spec/common.rb +5 -0
- data/spec/diesel.mp3 +0 -0
- data/spec/drive_info.spec.rb +75 -0
- data/spec/mouse.spec.rb +67 -0
- data/spec/parse_template.spec.rb +179 -0
- data/spec/play_mp3_audio.spec.rb +31 -0
- data/spec/ruby_clip.spec.rb +11 -0
- data/spec/run_drive_info.rb +4 -0
- data/spec/static.wav +0 -0
- data/spec/swing_helpers.spec.rb +140 -0
- data/vendor/dvdid.exe +0 -0
- data/vendor/mac_dvdid/bin/dvdid +0 -0
- data/vendor/mac_dvdid/include/dvdid/dvdid.h +67 -0
- data/vendor/mac_dvdid/include/dvdid/dvdid2.h +131 -0
- data/vendor/mac_dvdid/include/dvdid/export.h +32 -0
- data/vendor/mac_dvdid/lib/libdvdid.0.dylib +0 -0
- data/vendor/mac_dvdid/lib/libdvdid.a +0 -0
- data/vendor/mac_dvdid/lib/libdvdid.dylib +0 -0
- data/vendor/mac_dvdid/lib/libdvdid.la +41 -0
- metadata +110 -0
@@ -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
data/spec/diesel.mp3
ADDED
Binary file
|