simple_gui_creator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|