sweet 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +9 -5
- data/bin/sweet +3 -1
- data/examples/fox/hello_world.rb +12 -0
- data/examples/gtk2/hello_world.rb +14 -0
- data/examples/swing/hello_world.rb +12 -0
- data/examples/swt/components.rb +112 -0
- data/examples/swt/components/buttons.rb +35 -0
- data/examples/{hello_world.rb → swt/hello_world.rb} +2 -2
- data/examples/{snippet108.rb → swt/snippet108.rb} +2 -2
- data/examples/{snippet128.rb → swt/snippet128.rb} +7 -8
- data/examples/{snippet169.rb → swt/snippet169.rb} +2 -2
- data/examples/{snippet82.rb → swt/snippet82.rb} +2 -2
- data/examples/wx/hello_world.rb +12 -0
- data/lib/sweet.rb +22 -1
- data/lib/sweet/base.rb +151 -177
- data/lib/sweet/fox.rb +47 -0
- data/lib/sweet/fox/application.rb +9 -0
- data/lib/sweet/fox/object.rb +19 -0
- data/lib/sweet/gtk2.rb +56 -0
- data/lib/sweet/gtk2/application.rb +11 -0
- data/lib/sweet/gtk2/widget.rb +5 -0
- data/lib/sweet/hacks.rb +0 -17
- data/lib/sweet/swing.rb +97 -0
- data/lib/sweet/swing/application.rb +9 -0
- data/lib/sweet/swing/component.rb +23 -0
- data/lib/sweet/swing/frame.rb +13 -0
- data/lib/sweet/swt.rb +193 -0
- data/lib/sweet/swt/application.rb +17 -0
- data/lib/sweet/{composite.rb → swt/composite.rb} +5 -2
- data/lib/sweet/{dialog.rb → swt/dialog.rb} +0 -0
- data/lib/sweet/{downloader.rb → swt/downloader.rb} +1 -1
- data/lib/sweet/swt/shell.rb +22 -0
- data/lib/sweet/swt/src.zip +0 -0
- data/lib/sweet/swt/swt.jar +0 -0
- data/lib/sweet/swt/widget.rb +89 -0
- data/lib/sweet/wx.rb +57 -0
- data/lib/sweet/wx/application.rb +9 -0
- data/lib/sweet/wx/object.rb +19 -0
- data/spec/api_spec.rb +15 -0
- data/spec/downloader_spec.rb +28 -26
- data/spec/fox_spec.rb +15 -0
- data/spec/gtk2_spec.rb +15 -0
- data/spec/sweet_spec.rb +15 -0
- data/spec/swing_spec.rb +15 -0
- data/spec/swt_spec.rb +15 -0
- data/spec/wx_spec.rb +15 -0
- metadata +44 -17
- data/lib/sweet/shell.rb +0 -53
- data/lib/sweet/widget.rb +0 -151
data/lib/sweet/fox.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'sweet/base'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'fox16'
|
5
|
+
%w{object application}.each{|file| require "sweet/fox/#{file}"}
|
6
|
+
|
7
|
+
module Sweet
|
8
|
+
include Fox
|
9
|
+
|
10
|
+
# TODO integrate all standard widgets
|
11
|
+
WIDGET_HACKS = {
|
12
|
+
Wx::TextCtrl => {:custom_code => proc{ alias text get_value; alias text= set_value} }
|
13
|
+
}
|
14
|
+
|
15
|
+
default = {:init_args => :text}
|
16
|
+
WIDGET_DEFAULTS = {
|
17
|
+
:label => default,
|
18
|
+
:edit_line => default.merge(:class => TextCtrl),
|
19
|
+
:button => default.merge(:block_handler => :evt_button)
|
20
|
+
}
|
21
|
+
|
22
|
+
def self.create_app(name, opts, &block)
|
23
|
+
app = FXApp.new
|
24
|
+
class << app
|
25
|
+
include Sweet::Application
|
26
|
+
end
|
27
|
+
wnd = FXWindow.new(app, name)
|
28
|
+
wnd.sweeten(app, opts, &block)
|
29
|
+
app.create
|
30
|
+
wnd.show
|
31
|
+
app.run
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.create_widget_class(cls, init)
|
35
|
+
cls.is_a?(Class) ? cls : const_get('FX' + cls)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.initialize_widget(parent, cls, opts, init)
|
39
|
+
Sweet.debug "Creating #{cls}(#{parent})"
|
40
|
+
cls.new(parent, opts.delete(:text) || '')
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.delegate_events(cls)
|
44
|
+
# nothing to do
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Fox::Object
|
2
|
+
include Sweet::Component
|
3
|
+
|
4
|
+
def handle_event(evt, &block)
|
5
|
+
send evt, get_id do |event|
|
6
|
+
block.call
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def layout=(layout)
|
11
|
+
l = case layout
|
12
|
+
when :flow
|
13
|
+
Wx::BoxSizer.new(Wx::HORIZONTAL)
|
14
|
+
when :stack
|
15
|
+
Wx::BoxSizer.new(Wx::VERTICAL)
|
16
|
+
end
|
17
|
+
set_sizer l
|
18
|
+
end
|
19
|
+
end
|
data/lib/sweet/gtk2.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'sweet/base'
|
2
|
+
|
3
|
+
require 'gtk2'
|
4
|
+
%w{widget application}.each { |f| require "sweet/gtk2/#{f}" }
|
5
|
+
|
6
|
+
|
7
|
+
module Sweet
|
8
|
+
|
9
|
+
# TODO integrate all standard widgets
|
10
|
+
|
11
|
+
WIDGET_HACKS = {
|
12
|
+
}
|
13
|
+
|
14
|
+
default = {:init_args => :label}
|
15
|
+
WIDGET_DEFAULTS = {
|
16
|
+
:edit_line => default.merge(:class => Gtk::Entry),
|
17
|
+
:label => {:init_args => :text},
|
18
|
+
:button => default.merge(:block_handler => :clicked),
|
19
|
+
:flow => {:class => Gtk::HBox},
|
20
|
+
:stack => {:class => Gtk::VBox}
|
21
|
+
}
|
22
|
+
|
23
|
+
def self.create_app(name, opts, &block)
|
24
|
+
wnd = Gtk::Window.new
|
25
|
+
class << wnd; self; end.class_eval do
|
26
|
+
include Application
|
27
|
+
end
|
28
|
+
wnd.initialize_app wnd
|
29
|
+
wnd.sweeten(wnd, {:title => name}.merge(opts), &block)
|
30
|
+
wnd.show_all
|
31
|
+
wnd.signal_connect :destroy do
|
32
|
+
Gtk.main_quit
|
33
|
+
end
|
34
|
+
Gtk.main
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.create_widget_class(class_name, init)
|
38
|
+
class_name.is_a?(Class) ? class_name : Gtk.const_get(class_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.initialize_widget(parent, cls, opts, init)
|
42
|
+
Sweet.debug "Creating #{cls}(#{parent})"
|
43
|
+
widget = cls.new
|
44
|
+
if appender = init[:appender]
|
45
|
+
parent.send appender, widget
|
46
|
+
else
|
47
|
+
parent.add(widget)
|
48
|
+
end
|
49
|
+
widget
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.delegate_events(cls)
|
53
|
+
# nothing to do
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/lib/sweet/hacks.rb
CHANGED
@@ -1,20 +1,3 @@
|
|
1
|
-
class Hash
|
2
|
-
def unify
|
3
|
-
each_pair do |k, v|
|
4
|
-
if k.is_a? Array
|
5
|
-
k.each do |key|
|
6
|
-
self[key.to_sym] = v
|
7
|
-
end
|
8
|
-
self.delete k
|
9
|
-
elsif ! k.is_a?(Symbol)
|
10
|
-
self[k.to_sym] = v
|
11
|
-
self.delete k
|
12
|
-
end
|
13
|
-
end
|
14
|
-
self
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
1
|
class Symbol
|
19
2
|
# For configuring layouts
|
20
3
|
def conf(opts = {})
|
data/lib/sweet/swing.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'sweet/base'
|
2
|
+
|
3
|
+
require 'java'
|
4
|
+
%w{application component frame}.each { |f| require "sweet/swing/#{f}" }
|
5
|
+
|
6
|
+
|
7
|
+
module Sweet
|
8
|
+
|
9
|
+
import 'javax.swing'
|
10
|
+
|
11
|
+
# TODO integrate all standard widgets
|
12
|
+
|
13
|
+
WIDGET_HACKS = {
|
14
|
+
}
|
15
|
+
|
16
|
+
default = {:init_args => :text}
|
17
|
+
WIDGET_DEFAULTS = {
|
18
|
+
:edit_line => default.merge(:class => JTextField),
|
19
|
+
:label => default,
|
20
|
+
:button => default.merge(:block_handler => :on_action),
|
21
|
+
:menu_bar => {:appender => :setJMenuBar}
|
22
|
+
}
|
23
|
+
|
24
|
+
def self.create_app(name, opts, &block)
|
25
|
+
frame = JFrame.new(name)
|
26
|
+
# TODO factor this out
|
27
|
+
class << frame; self; end.class_eval do
|
28
|
+
include Application
|
29
|
+
end
|
30
|
+
frame.initialize_app frame
|
31
|
+
frame.default_close_operation = JFrame::EXIT_ON_CLOSE
|
32
|
+
frame.sweeten(opts, &block)
|
33
|
+
frame.visible = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.create_widget_class(class_name, init)
|
37
|
+
class_name.is_a?(Class) ? class_name : const_get('J' + class_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.initialize_widget(parent, cls, opts, init)
|
41
|
+
Sweet.debug "Creating #{cls.java_class.simple_name}(#{parent})"
|
42
|
+
widget = cls.new()
|
43
|
+
if appender = init[:appender]
|
44
|
+
parent.send appender, widget
|
45
|
+
else
|
46
|
+
if parent.respond_to?(:content_pane)
|
47
|
+
parent.content_pane
|
48
|
+
else
|
49
|
+
parent
|
50
|
+
end.add(widget)
|
51
|
+
end
|
52
|
+
widget
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.delegate_events(cls)
|
56
|
+
hacks = WIDGET_HACKS[cls]
|
57
|
+
default_listener = (hacks[:default_listener] || 'action').underscore if hacks
|
58
|
+
|
59
|
+
methods = cls.java_class.java_instance_methods
|
60
|
+
methods.each do |m|
|
61
|
+
next unless m.name =~ /(add(.*)Listener)/
|
62
|
+
j = $1
|
63
|
+
n = $2.underscore
|
64
|
+
p = m.parameter_types[0]
|
65
|
+
evts = p.declared_instance_methods
|
66
|
+
|
67
|
+
if evts.size == 1
|
68
|
+
s = "on_#{n}"
|
69
|
+
cls.send(:alias_method, s, j) unless cls.respond_to?(s)
|
70
|
+
else
|
71
|
+
evts.each do |evt|
|
72
|
+
s = evt.name.underscore
|
73
|
+
if s.split(/_/).size == 1 && n != default_listener
|
74
|
+
s = n + "_" + s
|
75
|
+
end
|
76
|
+
unused_events = evts.select{ |e| e.name != evt.name }.map{ |e| "def #{e.name.underscore}(e)\nend" }.join("\n")
|
77
|
+
cls.class_eval <<-EOF
|
78
|
+
def on_#{s}(&block)
|
79
|
+
l = Class.new do
|
80
|
+
include Java.#{p}
|
81
|
+
def initialize(&block)
|
82
|
+
@block = block
|
83
|
+
end
|
84
|
+
def #{evt.name}(e)
|
85
|
+
@block.call e
|
86
|
+
end
|
87
|
+
#{unused_events}
|
88
|
+
end
|
89
|
+
#{j} l.new(&block)
|
90
|
+
end
|
91
|
+
EOF
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This is the only base class of ALL Swing components
|
2
|
+
class Java::JavaAwt::Container
|
3
|
+
|
4
|
+
include Sweet::Component
|
5
|
+
|
6
|
+
def layout=(value, opts = {})
|
7
|
+
Sweet.debug "layout = #{name}(#{opts.inspect})"
|
8
|
+
layout = "#{value.to_s.capitalize}Layout" if value.is_a?(Symbol)
|
9
|
+
layout = instance_eval("awt.#{layout}") unless value.is_a?(Class)
|
10
|
+
|
11
|
+
l = layout.new
|
12
|
+
opts.each_pair do |k,v|
|
13
|
+
l.send("#{k}=", v)
|
14
|
+
end
|
15
|
+
|
16
|
+
content_pane.setLayout l
|
17
|
+
Sweet.debug content_pane.getLayout
|
18
|
+
end
|
19
|
+
|
20
|
+
def awt
|
21
|
+
java.awt
|
22
|
+
end
|
23
|
+
end
|
data/lib/sweet/swt.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'sweet/swt/downloader'
|
2
|
+
|
3
|
+
begin
|
4
|
+
load Sweet::SWT_JAR
|
5
|
+
rescue LoadError => e
|
6
|
+
STDERR.puts "No swt.jar found. Run:"
|
7
|
+
STDERR.puts " sweet install [toolkit]"
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'sweet/base'
|
12
|
+
|
13
|
+
require 'java'
|
14
|
+
%w{widget composite shell dialog application}.each{|file| require "sweet/swt/#{file}"}
|
15
|
+
|
16
|
+
module Sweet
|
17
|
+
|
18
|
+
import 'org.eclipse.swt'
|
19
|
+
import 'org.eclipse.swt.widgets'
|
20
|
+
import 'org.eclipse.swt.custom'
|
21
|
+
import 'org.eclipse.swt.browser'
|
22
|
+
|
23
|
+
# TODO integrate all standard widgets
|
24
|
+
WIDGET_HACKS = {
|
25
|
+
ProgressBar => {:custom_code => proc {
|
26
|
+
def fraction=(value)
|
27
|
+
setSelection value.to_f * maximum
|
28
|
+
end
|
29
|
+
def fraction
|
30
|
+
selection / maximum
|
31
|
+
end
|
32
|
+
}},
|
33
|
+
Text => {:custom_code => proc{
|
34
|
+
def password=(value)
|
35
|
+
self.echo_char = ?* if value
|
36
|
+
end
|
37
|
+
def password
|
38
|
+
! echo_char.nil?
|
39
|
+
end
|
40
|
+
}},
|
41
|
+
CTabFolder => {:default_listener => 'CTabFolder2'}
|
42
|
+
}
|
43
|
+
|
44
|
+
default = {:init_args => :text, :block_handler => SWT::Selection}
|
45
|
+
button_default = default.merge(:class => Button)
|
46
|
+
WIDGET_DEFAULTS = {
|
47
|
+
# Text components
|
48
|
+
:label => {:style => SWT::WRAP, :init_args => :text},
|
49
|
+
:edit_line => {:class => Text, :style => SWT::SINGLE | SWT::BORDER, :init_args => :text, :block_handler => :on_widget_default_selected},
|
50
|
+
:edit_area => {:class => Text, :style => SWT::MULTI | SWT::BORDER, :init_args => :text, :block_handler => :on_widget_default_selected},
|
51
|
+
|
52
|
+
# Buttons
|
53
|
+
:button => default.merge(:style => SWT::PUSH),
|
54
|
+
:toggle_button => button_default.merge(:style => SWT::TOGGLE),
|
55
|
+
:radio_button => button_default.merge(:style => SWT::RADIO),
|
56
|
+
:check_button => button_default.merge(:style => SWT::CHECK),
|
57
|
+
:arrow_button => button_default.merge(:style => SWT::ARROW),
|
58
|
+
|
59
|
+
# Menus
|
60
|
+
:menubar => {:class => Menu, :style => SWT::BAR},
|
61
|
+
:popup => {:class => Menu, :style => SWT::POP_UP},
|
62
|
+
:item => {:class => MenuItem, :style => SWT::PUSH, :init_args => :text, :block_handler => :on_widget_selected},
|
63
|
+
:separator => {:class => MenuItem, :style => SWT::SEPARATOR},
|
64
|
+
:submenu => {:class => MenuItem, :style => SWT::CASCADE, :init_args => :text, :block_handler => proc{|c, opts, block|
|
65
|
+
sub = Menu.new(c.app, SWT::DROP_DOWN)
|
66
|
+
sub.sweeten(c.app, opts, &block)
|
67
|
+
c.menu = sub
|
68
|
+
}
|
69
|
+
},
|
70
|
+
|
71
|
+
# Toolbars
|
72
|
+
:tool_item => default.merge(:style => SWT::PUSH),
|
73
|
+
# TODO make CoolBar work
|
74
|
+
# :cool_item => {:init_args => :control, :block_handler => proc{ |c, opts, proc|
|
75
|
+
# c.control = proc.call
|
76
|
+
# }},
|
77
|
+
|
78
|
+
# Tabs
|
79
|
+
:tab_folder => {:class => CTabFolder, :style => SWT::BORDER},
|
80
|
+
:tab_item => {:class => CTabItem, :style => SWT::CLOSE, :init_args => [:text, :control], :block_handler => proc{|c, opts, block|
|
81
|
+
c.control = block.call
|
82
|
+
}},
|
83
|
+
|
84
|
+
# Other components
|
85
|
+
:tree => {:style => SWT::VIRTUAL, :block_handler => SWT::Selection},
|
86
|
+
:progress => {:class => ProgressBar, :style => SWT::SMOOTH},
|
87
|
+
:group => {:init_args => :text},
|
88
|
+
|
89
|
+
# Dialogs
|
90
|
+
:color_dialog => {:style => SWT::APPLICATION_MODAL, :init_args => :text},
|
91
|
+
:modal_dialog => {:style => SWT::APPLICATION_MODAL, :init_args => :text, :block_handler => proc { |c, opts, block|
|
92
|
+
meta.class_eval do
|
93
|
+
def open
|
94
|
+
shell = widgets::Shell.new(app, swt::DIALOG_TRIM | style)
|
95
|
+
shell.text = text
|
96
|
+
shell.app &block
|
97
|
+
shell.open
|
98
|
+
while !shell.isDisposed
|
99
|
+
app.display.sleep unless display.readAndDispatch()
|
100
|
+
return result
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
def self.create_app(name, opts, &block)
|
109
|
+
# build the UI
|
110
|
+
Display.setAppName(name)
|
111
|
+
display = Display.new
|
112
|
+
|
113
|
+
# TODO allow multiple shells
|
114
|
+
shell = Shell.new(display)
|
115
|
+
class << shell; self; end.class_eval do
|
116
|
+
include Application
|
117
|
+
end
|
118
|
+
shell.initialize_app shell
|
119
|
+
shell.sweeten(display, name, opts, &block)
|
120
|
+
shell.open
|
121
|
+
|
122
|
+
# dispatch loop
|
123
|
+
while (!shell.isDisposed) do
|
124
|
+
display.sleep unless display.readAndDispatch
|
125
|
+
end
|
126
|
+
|
127
|
+
display.dispose
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.create_widget_class(cls, init)
|
131
|
+
cls.is_a?(Class) ? cls : const_get(cls)
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.initialize_widget(parent, cls, opts, init)
|
135
|
+
style = to_style(opts.delete(:style) || init[:style] || SWT::NONE)
|
136
|
+
style |= to_style(opts.delete(:add_style) || 0)
|
137
|
+
|
138
|
+
Sweet.debug "Creating #{cls.java_class.simple_name}(#{parent}, #{style})"
|
139
|
+
cls.new(parent, style)
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.delegate_events(cls)
|
143
|
+
hacks = WIDGET_HACKS[cls]
|
144
|
+
default_listener = (hacks[:default_listener] || '').underscore if hacks
|
145
|
+
|
146
|
+
methods = cls.java_class.java_instance_methods
|
147
|
+
methods.each do |m|
|
148
|
+
next unless m.name =~ /(add(.*)Listener)/
|
149
|
+
j = $1
|
150
|
+
n = $2.underscore
|
151
|
+
p = m.parameter_types[0]
|
152
|
+
evts = p.declared_instance_methods
|
153
|
+
|
154
|
+
if evts.size == 1
|
155
|
+
s = "on_#{n}"
|
156
|
+
cls.send(:alias_method, s, j) unless cls.respond_to?(s)
|
157
|
+
else
|
158
|
+
evts.each do |evt|
|
159
|
+
s = evt.name.underscore
|
160
|
+
if s.split(/_/).size == 1 && n != default_listener
|
161
|
+
s = n + "_" + s
|
162
|
+
end
|
163
|
+
# TODO provide addListener SWT::Xxx option
|
164
|
+
unused_events = evts.select{ |e| e.name != evt.name }.map{ |e| "def #{e.name.underscore}(e)\nend" }.join("\n")
|
165
|
+
cls.class_eval <<-EOF
|
166
|
+
def on_#{s}(&block)
|
167
|
+
l = Class.new do
|
168
|
+
include Java.#{p}
|
169
|
+
def initialize(&block)
|
170
|
+
@block = block
|
171
|
+
end
|
172
|
+
def #{evt.name}(e)
|
173
|
+
@block.call e
|
174
|
+
end
|
175
|
+
#{unused_events}
|
176
|
+
end
|
177
|
+
#{j} l.new(&block)
|
178
|
+
end
|
179
|
+
EOF
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
def self.to_style(style)
|
187
|
+
Array(style).inject(0) do |result, value|
|
188
|
+
value = value.swt_const if value.is_a? Symbol
|
189
|
+
result | value
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|