sweet 0.0.2 → 0.0.3

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.
Files changed (49) hide show
  1. data/README.rdoc +9 -5
  2. data/bin/sweet +3 -1
  3. data/examples/fox/hello_world.rb +12 -0
  4. data/examples/gtk2/hello_world.rb +14 -0
  5. data/examples/swing/hello_world.rb +12 -0
  6. data/examples/swt/components.rb +112 -0
  7. data/examples/swt/components/buttons.rb +35 -0
  8. data/examples/{hello_world.rb → swt/hello_world.rb} +2 -2
  9. data/examples/{snippet108.rb → swt/snippet108.rb} +2 -2
  10. data/examples/{snippet128.rb → swt/snippet128.rb} +7 -8
  11. data/examples/{snippet169.rb → swt/snippet169.rb} +2 -2
  12. data/examples/{snippet82.rb → swt/snippet82.rb} +2 -2
  13. data/examples/wx/hello_world.rb +12 -0
  14. data/lib/sweet.rb +22 -1
  15. data/lib/sweet/base.rb +151 -177
  16. data/lib/sweet/fox.rb +47 -0
  17. data/lib/sweet/fox/application.rb +9 -0
  18. data/lib/sweet/fox/object.rb +19 -0
  19. data/lib/sweet/gtk2.rb +56 -0
  20. data/lib/sweet/gtk2/application.rb +11 -0
  21. data/lib/sweet/gtk2/widget.rb +5 -0
  22. data/lib/sweet/hacks.rb +0 -17
  23. data/lib/sweet/swing.rb +97 -0
  24. data/lib/sweet/swing/application.rb +9 -0
  25. data/lib/sweet/swing/component.rb +23 -0
  26. data/lib/sweet/swing/frame.rb +13 -0
  27. data/lib/sweet/swt.rb +193 -0
  28. data/lib/sweet/swt/application.rb +17 -0
  29. data/lib/sweet/{composite.rb → swt/composite.rb} +5 -2
  30. data/lib/sweet/{dialog.rb → swt/dialog.rb} +0 -0
  31. data/lib/sweet/{downloader.rb → swt/downloader.rb} +1 -1
  32. data/lib/sweet/swt/shell.rb +22 -0
  33. data/lib/sweet/swt/src.zip +0 -0
  34. data/lib/sweet/swt/swt.jar +0 -0
  35. data/lib/sweet/swt/widget.rb +89 -0
  36. data/lib/sweet/wx.rb +57 -0
  37. data/lib/sweet/wx/application.rb +9 -0
  38. data/lib/sweet/wx/object.rb +19 -0
  39. data/spec/api_spec.rb +15 -0
  40. data/spec/downloader_spec.rb +28 -26
  41. data/spec/fox_spec.rb +15 -0
  42. data/spec/gtk2_spec.rb +15 -0
  43. data/spec/sweet_spec.rb +15 -0
  44. data/spec/swing_spec.rb +15 -0
  45. data/spec/swt_spec.rb +15 -0
  46. data/spec/wx_spec.rb +15 -0
  47. metadata +44 -17
  48. data/lib/sweet/shell.rb +0 -53
  49. 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,9 @@
1
+ module Sweet::Application
2
+ def perform(&block)
3
+ instance_eval &block
4
+ end
5
+
6
+ def busy(&block)
7
+ instance_eval &block
8
+ end
9
+ 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
@@ -0,0 +1,11 @@
1
+ module Sweet::Application
2
+ def perform(&block)
3
+ # TODO implement
4
+ instance_eval &block
5
+ end
6
+
7
+ def busy(&block)
8
+ # TODO implement
9
+ instance_eval &block
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class Gtk::Widget
2
+ include Sweet::Component
3
+ alias handle_event signal_connect
4
+ end
5
+
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 = {})
@@ -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,9 @@
1
+ module Sweet::Application
2
+ def perform(&block)
3
+ SwingUtilities.invoke_and_wait &block
4
+ end
5
+
6
+ def busy(&block)
7
+ SwingUtilities.invoke_later &block
8
+ end
9
+ 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
@@ -0,0 +1,13 @@
1
+ class Java::JavaxSwing::JFrame
2
+ alias_property :text => :title
3
+
4
+ def sweeten(opts = {}, &block)
5
+ w, h = opts.delete(:width), opts.delete(:height)
6
+
7
+ super(self, opts, &block)
8
+
9
+ pack
10
+ size = (w || width), (h || height)
11
+ end
12
+
13
+ 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