glimmer-dsl-swt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +47 -0
  4. data/RUBY_VERSION +1 -0
  5. data/VERSION +1 -0
  6. data/bin/girb +10 -0
  7. data/bin/girb_runner.rb +13 -0
  8. data/bin/glimmer +5 -0
  9. data/icons/scaffold_app.icns +0 -0
  10. data/lib/ext/glimmer.rb +13 -0
  11. data/lib/ext/glimmer/config.rb +18 -0
  12. data/lib/glimmer-dsl-swt.rb +12 -0
  13. data/lib/glimmer/data_binding/list_selection_binding.rb +52 -0
  14. data/lib/glimmer/data_binding/model_binding.rb +248 -0
  15. data/lib/glimmer/data_binding/observable.rb +21 -0
  16. data/lib/glimmer/data_binding/observable_array.rb +107 -0
  17. data/lib/glimmer/data_binding/observable_model.rb +108 -0
  18. data/lib/glimmer/data_binding/observable_widget.rb +17 -0
  19. data/lib/glimmer/data_binding/observer.rb +124 -0
  20. data/lib/glimmer/data_binding/shine.rb +23 -0
  21. data/lib/glimmer/data_binding/table_items_binding.rb +56 -0
  22. data/lib/glimmer/data_binding/tree_items_binding.rb +71 -0
  23. data/lib/glimmer/data_binding/widget_binding.rb +33 -0
  24. data/lib/glimmer/dsl/swt/async_exec_expression.rb +14 -0
  25. data/lib/glimmer/dsl/swt/bind_expression.rb +37 -0
  26. data/lib/glimmer/dsl/swt/color_expression.rb +19 -0
  27. data/lib/glimmer/dsl/swt/column_properties_expression.rb +24 -0
  28. data/lib/glimmer/dsl/swt/combo_selection_data_binding_expression.rb +42 -0
  29. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +39 -0
  30. data/lib/glimmer/dsl/swt/data_binding_expression.rb +34 -0
  31. data/lib/glimmer/dsl/swt/dialog_expression.rb +26 -0
  32. data/lib/glimmer/dsl/swt/display_expression.rb +19 -0
  33. data/lib/glimmer/dsl/swt/dsl.rb +34 -0
  34. data/lib/glimmer/dsl/swt/exec_expression.rb +28 -0
  35. data/lib/glimmer/dsl/swt/layout_data_expression.rb +25 -0
  36. data/lib/glimmer/dsl/swt/layout_expression.rb +27 -0
  37. data/lib/glimmer/dsl/swt/list_selection_data_binding_expression.rb +44 -0
  38. data/lib/glimmer/dsl/swt/menu_bar_expression.rb +33 -0
  39. data/lib/glimmer/dsl/swt/menu_expression.rb +32 -0
  40. data/lib/glimmer/dsl/swt/message_box_expression.rb +29 -0
  41. data/lib/glimmer/dsl/swt/observe_expression.rb +32 -0
  42. data/lib/glimmer/dsl/swt/property_expression.rb +22 -0
  43. data/lib/glimmer/dsl/swt/rgb_expression.rb +12 -0
  44. data/lib/glimmer/dsl/swt/rgba_expression.rb +12 -0
  45. data/lib/glimmer/dsl/swt/shell_expression.rb +25 -0
  46. data/lib/glimmer/dsl/swt/swt_expression.rb +25 -0
  47. data/lib/glimmer/dsl/swt/sync_exec_expression.rb +15 -0
  48. data/lib/glimmer/dsl/swt/tab_item_expression.rb +33 -0
  49. data/lib/glimmer/dsl/swt/table_items_data_binding_expression.rb +31 -0
  50. data/lib/glimmer/dsl/swt/tree_items_data_binding_expression.rb +31 -0
  51. data/lib/glimmer/dsl/swt/tree_properties_expression.rb +26 -0
  52. data/lib/glimmer/dsl/swt/widget_expression.rb +35 -0
  53. data/lib/glimmer/dsl/swt/widget_listener_expression.rb +32 -0
  54. data/lib/glimmer/launcher.rb +196 -0
  55. data/lib/glimmer/package.rb +57 -0
  56. data/lib/glimmer/rake_task.rb +62 -0
  57. data/lib/glimmer/scaffold.rb +582 -0
  58. data/lib/glimmer/swt/color_proxy.rb +53 -0
  59. data/lib/glimmer/swt/display_proxy.rb +88 -0
  60. data/lib/glimmer/swt/font_proxy.rb +72 -0
  61. data/lib/glimmer/swt/layout_data_proxy.rb +84 -0
  62. data/lib/glimmer/swt/layout_proxy.rb +82 -0
  63. data/lib/glimmer/swt/menu_proxy.rb +101 -0
  64. data/lib/glimmer/swt/message_box_proxy.rb +48 -0
  65. data/lib/glimmer/swt/packages.rb +13 -0
  66. data/lib/glimmer/swt/shell_proxy.rb +152 -0
  67. data/lib/glimmer/swt/swt_proxy.rb +106 -0
  68. data/lib/glimmer/swt/tab_item_proxy.rb +65 -0
  69. data/lib/glimmer/swt/table_proxy.rb +150 -0
  70. data/lib/glimmer/swt/tree_proxy.rb +120 -0
  71. data/lib/glimmer/swt/widget_listener_proxy.rb +34 -0
  72. data/lib/glimmer/swt/widget_proxy.rb +489 -0
  73. data/lib/glimmer/ui/custom_shell.rb +45 -0
  74. data/lib/glimmer/ui/custom_widget.rb +244 -0
  75. data/lib/glimmer/util/proc_tracker.rb +16 -0
  76. data/vendor/swt/linux/swt.jar +0 -0
  77. data/vendor/swt/mac/swt.jar +0 -0
  78. data/vendor/swt/windows/swt.jar +0 -0
  79. metadata +307 -0
@@ -0,0 +1,120 @@
1
+ require 'glimmer/swt/widget_proxy'
2
+
3
+ module Glimmer
4
+ module SWT
5
+ class TreeProxy < Glimmer::SWT::WidgetProxy
6
+ include Glimmer
7
+
8
+ attr_reader :tree_editor, :tree_editor_text_proxy
9
+ attr_accessor :tree_properties
10
+
11
+ def initialize(underscored_widget_name, parent, args)
12
+ super
13
+ @tree_editor = TreeEditor.new(swt_widget)
14
+ @tree_editor.horizontalAlignment = SWTProxy[:left]
15
+ @tree_editor.grabHorizontal = true
16
+ @tree_editor.minimumHeight = 20
17
+ end
18
+
19
+ # Performs depth first search for tree items matching block condition
20
+ # If no condition block is passed, returns all tree items
21
+ # Returns a Java TreeItem array to easily set as selection on org.eclipse.swt.Tree if needed
22
+ def depth_first_search(&condition)
23
+ found = []
24
+ recursive_depth_first_search(swt_widget.getItems.first, found, &condition)
25
+ found.to_java(TreeItem)
26
+ end
27
+
28
+ # Returns all tree items including descendants
29
+ def all_tree_items
30
+ depth_first_search
31
+ end
32
+
33
+ def widget_property_listener_installers
34
+ super.merge({
35
+ Java::OrgEclipseSwtWidgets::Tree => {
36
+ selection: lambda do |observer|
37
+ on_widget_selected { |selection_event|
38
+ observer.call(@swt_widget.getSelection)
39
+ }
40
+ end
41
+ },
42
+ })
43
+ end
44
+
45
+ def edit_in_progress?
46
+ !!@edit_in_progress
47
+ end
48
+
49
+ def edit_selected_tree_item(before_write: nil, after_write: nil, after_cancel: nil)
50
+ edit_tree_item(swt_widget.getSelection.first, before_write: before_write, after_write: after_write, after_cancel: after_cancel)
51
+ end
52
+
53
+ def edit_tree_item(tree_item, before_write: nil, after_write: nil, after_cancel: nil)
54
+ return if tree_item.nil?
55
+ content {
56
+ @tree_editor_text_proxy = text {
57
+ focus true
58
+ text tree_item.getText
59
+ action_taken = false
60
+ cancel = lambda {
61
+ @tree_editor_text_proxy.swt_widget.dispose
62
+ @tree_editor_text_proxy = nil
63
+ after_cancel&.call
64
+ @edit_in_progress = false
65
+ }
66
+ action = lambda { |event|
67
+ if !action_taken && !@edit_in_progress
68
+ action_taken = true
69
+ @edit_in_progress = true
70
+ new_text = @tree_editor_text_proxy.swt_widget.getText
71
+ if new_text == tree_item.getText
72
+ cancel.call
73
+ else
74
+ before_write&.call
75
+ tree_item.setText(new_text)
76
+ model = tree_item.getData
77
+ model.send("#{tree_properties[:text]}=", new_text) # makes tree update itself, so must search for selected tree item again
78
+ edited_tree_item = depth_first_search { |ti| ti.getData == model }.first
79
+ swt_widget.showItem(edited_tree_item)
80
+ @tree_editor_text_proxy.swt_widget.dispose
81
+ @tree_editor_text_proxy = nil
82
+ after_write&.call(edited_tree_item)
83
+ @edit_in_progress = false
84
+ end
85
+ end
86
+ }
87
+ on_focus_lost(&action)
88
+ on_key_pressed { |key_event|
89
+ if key_event.keyCode == swt(:cr)
90
+ action.call(key_event)
91
+ elsif key_event.keyCode == swt(:esc)
92
+ cancel.call
93
+ end
94
+ }
95
+ }
96
+ @tree_editor_text_proxy.swt_widget.selectAll
97
+ }
98
+ @tree_editor.setEditor(@tree_editor_text_proxy.swt_widget, tree_item)
99
+ end
100
+
101
+ private
102
+
103
+ def recursive_depth_first_search(tree_item, found, &condition)
104
+ return if tree_item.nil?
105
+ found << tree_item if condition.nil? || condition.call(tree_item)
106
+ tree_item.getItems.each do |child_tree_item|
107
+ recursive_depth_first_search(child_tree_item, found, &condition)
108
+ end
109
+ end
110
+
111
+ def property_type_converters
112
+ super.merge({
113
+ selection: lambda do |value|
114
+ depth_first_search {|ti| ti.getData == value}
115
+ end,
116
+ })
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,34 @@
1
+ module Glimmer
2
+ module SWT
3
+ # Proxy for widget listeners
4
+ #
5
+ # Follows the Proxy Design Pattern
6
+ class WidgetListenerProxy
7
+
8
+ attr_reader :swt_widget, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
9
+
10
+ def initialize(swt_widget:, swt_listener:, widget_add_listener_method: nil, swt_listener_class: nil, swt_listener_method: nil, event_type: nil, swt_constant: nil)
11
+ @swt_widget = swt_widget
12
+ @swt_listener = swt_listener
13
+ @widget_add_listener_method = widget_add_listener_method
14
+ @swt_listener_class = swt_listener_class
15
+ @swt_listener_method = swt_listener_method
16
+ @event_type = event_type
17
+ @swt_constant = swt_constant
18
+ end
19
+
20
+ def widget_remove_listener_method
21
+ @widget_add_listener_method.sub('add', 'remove')
22
+ end
23
+
24
+ def unregister
25
+ # TODO consider renaming to deregister (and in Observer too)
26
+ if @event_type
27
+ @swt_widget.removeListener(@event_type, @swt_listener)
28
+ else
29
+ @swt_widget.send(widget_remove_listener_method, @swt_listener)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,489 @@
1
+ require 'glimmer'
2
+ require 'glimmer/swt/widget_listener_proxy'
3
+ require 'glimmer/swt/color_proxy'
4
+ require 'glimmer/swt/font_proxy'
5
+ require 'glimmer/swt/swt_proxy'
6
+ require 'glimmer/data_binding/observable_widget'
7
+
8
+ # TODO refactor to make file smaller and extract sub-widget-proxies out of this
9
+
10
+ module Glimmer
11
+ module SWT
12
+ # Proxy for SWT Widget objects
13
+ #
14
+ # Sets default SWT styles to widgets upon inititalizing as
15
+ # per DEFAULT_STYLES
16
+ #
17
+ # Also, auto-initializes widgets as per initializer blocks
18
+ # in DEFAULT_INITIALIZERS (e.g. setting Composite default layout)
19
+ #
20
+ # Follows the Proxy Design Pattern
21
+ class WidgetProxy
22
+ include Packages
23
+ include DataBinding::ObservableWidget
24
+
25
+ DEFAULT_STYLES = {
26
+ "text" => [:border],
27
+ "table" => [:border],
28
+ "tree" => [:virtual, :border, :h_scroll, :v_scroll],
29
+ "spinner" => [:border],
30
+ "styled_text" => [:border],
31
+ "list" => [:border, :v_scroll],
32
+ "button" => [:push],
33
+ "menu_item" => [:push],
34
+ }
35
+
36
+ DEFAULT_INITIALIZERS = {
37
+ "composite" => lambda do |composite|
38
+ composite.setLayout(GridLayout.new)
39
+ end,
40
+ "table" => lambda do |table|
41
+ table.setHeaderVisible(true)
42
+ table.setLinesVisible(true)
43
+ end,
44
+ "table_column" => lambda do |table_column|
45
+ table_column.setWidth(80)
46
+ end,
47
+ "group" => lambda do |group|
48
+ group.setLayout(GridLayout.new)
49
+ end,
50
+ }
51
+
52
+ attr_reader :swt_widget
53
+
54
+ # Initializes a new SWT Widget
55
+ #
56
+ # Styles is a comma separate list of symbols representing SWT styles in lower case
57
+ def initialize(underscored_widget_name, parent, args)
58
+ styles, extra_options = extract_args(underscored_widget_name, args)
59
+ swt_widget_class = self.class.swt_widget_class_for(underscored_widget_name)
60
+ @swt_widget = swt_widget_class.new(parent.swt_widget, style(underscored_widget_name, styles), *extra_options)
61
+ DEFAULT_INITIALIZERS[underscored_widget_name]&.call(@swt_widget)
62
+ end
63
+
64
+ def extract_args(underscored_widget_name, args)
65
+ @arg_extractor_mapping ||= {
66
+ 'menu_item' => lambda do |args|
67
+ index = args.delete(args.last) if args.last.is_a?(Numeric)
68
+ extra_options = [index].compact
69
+ styles = args
70
+ [styles, extra_options]
71
+ end,
72
+ }
73
+ if @arg_extractor_mapping[underscored_widget_name]
74
+ @arg_extractor_mapping[underscored_widget_name].call(args)
75
+ else
76
+ [args, []]
77
+ end
78
+ end
79
+
80
+ def has_attribute?(attribute_name, *args)
81
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
82
+ if widget_custom_attribute
83
+ @swt_widget.respond_to?(widget_custom_attribute[:setter][:name])
84
+ else
85
+ @swt_widget.respond_to?(attribute_setter(attribute_name), args)
86
+ end
87
+ end
88
+
89
+ def set_attribute(attribute_name, *args)
90
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
91
+ if widget_custom_attribute
92
+ widget_custom_attribute[:setter][:invoker].call(@swt_widget, args)
93
+ else
94
+ apply_property_type_converters(attribute_name, args)
95
+ @swt_widget.send(attribute_setter(attribute_name), *args) unless @swt_widget.send(attribute_getter(attribute_name)) == args.first
96
+ end
97
+ end
98
+
99
+ def get_attribute(attribute_name)
100
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
101
+ if widget_custom_attribute
102
+ @swt_widget.send(widget_custom_attribute[:getter][:name])
103
+ else
104
+ @swt_widget.send(attribute_getter(attribute_name))
105
+ end
106
+ end
107
+
108
+ def pack_same_size
109
+ bounds = @swt_widget.getBounds
110
+ listener = on_control_resized {
111
+ @swt_widget.setSize(bounds.width, bounds.height)
112
+ @swt_widget.setLocation(bounds.x, bounds.y)
113
+ }
114
+ if @swt_widget.is_a?(Composite)
115
+ @swt_widget.layout(true, true)
116
+ else
117
+ @swt_widget.pack(true)
118
+ end
119
+ @swt_widget.removeControlListener(listener.swt_listener)
120
+ end
121
+
122
+ def widget_property_listener_installers
123
+ @swt_widget_property_listener_installers ||= {
124
+ Java::OrgEclipseSwtWidgets::Control => {
125
+ :focus => lambda do |observer|
126
+ on_focus_gained { |focus_event|
127
+ observer.call(true)
128
+ }
129
+ on_focus_lost { |focus_event|
130
+ observer.call(false)
131
+ }
132
+ end,
133
+ },
134
+ Java::OrgEclipseSwtWidgets::Text => {
135
+ :text => lambda do |observer|
136
+ on_modify_text { |modify_event|
137
+ observer.call(@swt_widget.getText)
138
+ }
139
+ end,
140
+ :caret_position => lambda do |observer|
141
+ on_event_keydown { |event|
142
+ observer.call(@swt_widget.getCaretPosition)
143
+ }
144
+ on_event_keyup { |event|
145
+ observer.call(@swt_widget.getCaretPosition)
146
+ }
147
+ on_event_mousedown { |event|
148
+ observer.call(@swt_widget.getCaretPosition)
149
+ }
150
+ on_event_mouseup { |event|
151
+ observer.call(@swt_widget.getCaretPosition)
152
+ }
153
+ end,
154
+ :selection => lambda do |observer|
155
+ on_event_keydown { |event|
156
+ observer.call(@swt_widget.getSelection)
157
+ }
158
+ on_event_keyup { |event|
159
+ observer.call(@swt_widget.getSelection)
160
+ }
161
+ on_event_mousedown { |event|
162
+ observer.call(@swt_widget.getSelection)
163
+ }
164
+ on_event_mouseup { |event|
165
+ observer.call(@swt_widget.getSelection)
166
+ }
167
+ end,
168
+ :selection_count => lambda do |observer|
169
+ on_event_keydown { |event|
170
+ observer.call(@swt_widget.getSelectionCount)
171
+ }
172
+ on_event_keyup { |event|
173
+ observer.call(@swt_widget.getSelectionCount)
174
+ }
175
+ on_event_mousedown { |event|
176
+ observer.call(@swt_widget.getSelectionCount)
177
+ }
178
+ on_event_mouseup { |event|
179
+ observer.call(@swt_widget.getSelectionCount)
180
+ }
181
+ end,
182
+ :top_index => lambda do |observer|
183
+ @last_top_index = @swt_widget.getTopIndex
184
+ on_paint_control { |event|
185
+ if @swt_widget.getTopIndex != @last_top_index
186
+ @last_top_index = @swt_widget.getTopIndex
187
+ observer.call(@last_top_index)
188
+ end
189
+ }
190
+ end,
191
+ },
192
+ Java::OrgEclipseSwtCustom::StyledText => {
193
+ :text => lambda do |observer|
194
+ on_modify_text { |modify_event|
195
+ observer.call(@swt_widget.getText)
196
+ }
197
+ end,
198
+ },
199
+ Java::OrgEclipseSwtWidgets::Button => {
200
+ :selection => lambda do |observer|
201
+ on_widget_selected { |selection_event|
202
+ observer.call(@swt_widget.getSelection)
203
+ }
204
+ end
205
+ },
206
+ Java::OrgEclipseSwtWidgets::MenuItem => {
207
+ :selection => lambda do |observer|
208
+ on_widget_selected { |selection_event|
209
+ observer.call(@swt_widget.getSelection)
210
+ }
211
+ end
212
+ },
213
+ Java::OrgEclipseSwtWidgets::Spinner => {
214
+ :selection => lambda do |observer|
215
+ on_widget_selected { |selection_event|
216
+ observer.call(@swt_widget.getSelection)
217
+ }
218
+ end
219
+ },
220
+ }
221
+ end
222
+
223
+ def self.widget_exists?(underscored_widget_name)
224
+ !!swt_widget_class_for(underscored_widget_name)
225
+ end
226
+
227
+ # This supports widgets in and out of basic SWT
228
+ def self.swt_widget_class_for(underscored_widget_name)
229
+ swt_widget_name = underscored_widget_name.camelcase(:upper)
230
+ swt_widget_class = eval(swt_widget_name)
231
+ unless swt_widget_class.ancestors.include?(org.eclipse.swt.widgets.Widget)
232
+ Glimmer::Config.logger&.debug("Class #{swt_widget_class} matching #{underscored_widget_name} is not a subclass of org.eclipse.swt.widgets.Widget")
233
+ return nil
234
+ end
235
+ swt_widget_class
236
+ rescue NameError => e
237
+ Glimmer::Config.logger&.debug e.message
238
+ # Glimmer::Config.logger&.debug("#{e.message}\n#{e.backtrace.join("\n")}")
239
+ nil
240
+ rescue => e
241
+ Glimmer::Config.logger&.debug e.message
242
+ # Glimmer::Config.logger&.debug("#{e.message}\n#{e.backtrace.join("\n")}")
243
+ nil
244
+ end
245
+
246
+ def async_exec(&block)
247
+ DisplayProxy.instance.async_exec(&block)
248
+ end
249
+
250
+ def sync_exec(&block)
251
+ DisplayProxy.instance.sync_exec(&block)
252
+ end
253
+
254
+ def has_style?(style)
255
+ (@swt_widget.style & SWTProxy[style]) == SWTProxy[style]
256
+ end
257
+
258
+ def dispose
259
+ @swt_widget.dispose
260
+ end
261
+
262
+ # TODO Consider renaming these methods as they are mainly used for data-binding
263
+
264
+ def can_add_observer?(property_name)
265
+ @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact.map(&:keys).flatten.map(&:to_s).include?(property_name.to_s)
266
+ end
267
+
268
+ # Used for data-binding only. Consider renaming or improving to avoid the confusion it causes
269
+ def add_observer(observer, property_name)
270
+ property_listener_installers = @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
271
+ widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
272
+ widget_listener_installers.to_a.each do |widget_listener_installer|
273
+ widget_listener_installer.call(observer)
274
+ end
275
+ end
276
+
277
+ def remove_observer(observer, property_name)
278
+ # TODO consider implementing if remove_observer is needed (consumers can remove listener via SWT API)
279
+ end
280
+
281
+ # TODO eliminate duplication in the following methods perhaps by relying on exceptions
282
+
283
+ def can_handle_observation_request?(observation_request)
284
+ observation_request = observation_request.to_s
285
+ if observation_request.start_with?('on_event_')
286
+ constant_name = observation_request.sub(/^on_event_/, '')
287
+ SWTProxy.has_constant?(constant_name)
288
+ elsif observation_request.start_with?('on_')
289
+ event = observation_request.sub(/^on_/, '')
290
+ can_add_listener?(event)
291
+ else
292
+ false
293
+ end
294
+ end
295
+
296
+ def handle_observation_request(observation_request, &block)
297
+ if observation_request.start_with?('on_event_')
298
+ constant_name = observation_request.sub(/^on_event_/, '')
299
+ add_swt_event_listener(constant_name, &block)
300
+ elsif observation_request.start_with?('on_')
301
+ event = observation_request.sub(/^on_/, '')
302
+ add_listener(event, &block)
303
+ end
304
+ end
305
+
306
+ def content(&block)
307
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, &block)
308
+ end
309
+
310
+ private
311
+
312
+ def style(underscored_widget_name, styles)
313
+ styles = [styles].flatten.compact
314
+ styles.empty? ? default_style(underscored_widget_name) : SWTProxy[*styles]
315
+ end
316
+
317
+ def default_style(underscored_widget_name)
318
+ styles = DEFAULT_STYLES[underscored_widget_name] || [:none]
319
+ SWTProxy[styles]
320
+ end
321
+
322
+ def attribute_setter(attribute_name)
323
+ "set#{attribute_name.to_s.camelcase(:upper)}"
324
+ end
325
+
326
+ def attribute_getter(attribute_name)
327
+ "get#{attribute_name.to_s.camelcase(:upper)}"
328
+ end
329
+
330
+ # TODO refactor following methods to eliminate duplication
331
+ # perhaps consider relying on raising an exception to avoid checking first
332
+ # unless that gives obscure SWT errors
333
+ # Otherwise, consider caching results from can_add_lsitener and using them in
334
+ # add_listener knowing it will be called for sure afterwards
335
+
336
+ def can_add_listener?(underscored_listener_name)
337
+ !self.class.find_listener(@swt_widget.getClass, underscored_listener_name).empty?
338
+ end
339
+
340
+ def add_listener(underscored_listener_name, &block)
341
+ widget_add_listener_method, listener_class, listener_method = self.class.find_listener(@swt_widget.getClass, underscored_listener_name)
342
+ widget_listener_proxy = nil
343
+ safe_block = lambda { |event| block.call(event) unless @swt_widget.isDisposed }
344
+ listener = listener_class.new(listener_method => safe_block)
345
+ @swt_widget.send(widget_add_listener_method, listener)
346
+ widget_listener_proxy = WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: listener, widget_add_listener_method: widget_add_listener_method, swt_listener_class: listener_class, swt_listener_method: listener_method)
347
+ end
348
+
349
+ # Looks through SWT class add***Listener methods till it finds one for which
350
+ # the argument is a listener class that has an event method matching
351
+ # underscored_listener_name
352
+ def self.find_listener(swt_widget_class, underscored_listener_name)
353
+ @listeners ||= {}
354
+ listener_key = [swt_widget_class.name, underscored_listener_name]
355
+ unless @listeners.has_key?(listener_key)
356
+ listener_method_name = underscored_listener_name.camelcase(:lower)
357
+ swt_widget_class.getMethods.each do |widget_add_listener_method|
358
+ if widget_add_listener_method.getName.match(/add.*Listener/)
359
+ widget_add_listener_method.getParameterTypes.each do |listener_type|
360
+ listener_type.getMethods.each do |listener_method|
361
+ if (listener_method.getName == listener_method_name)
362
+ @listeners[listener_key] = [widget_add_listener_method.getName, listener_class(listener_type), listener_method.getName]
363
+ return @listeners[listener_key]
364
+ end
365
+ end
366
+ end
367
+ end
368
+ end
369
+ @listeners[listener_key] = []
370
+ end
371
+ @listeners[listener_key]
372
+ end
373
+
374
+ # Returns a Ruby class that implements listener type Java interface with ability to easily
375
+ # install a block that gets called upon calling a listener event method
376
+ def self.listener_class(listener_type)
377
+ @listener_classes ||= {}
378
+ listener_class_key = listener_type.name
379
+ unless @listener_classes.has_key?(listener_class_key)
380
+ @listener_classes[listener_class_key] = Class.new(Object).tap do |listener_class|
381
+ listener_class.send :include, (eval listener_type.name.sub("interface", ""))
382
+ listener_class.define_method('initialize') do |event_method_block_mapping|
383
+ @event_method_block_mapping = event_method_block_mapping
384
+ end
385
+ listener_type.getMethods.each do |event_method|
386
+ listener_class.define_method(event_method.getName) do |event|
387
+ @event_method_block_mapping[event_method.getName]&.call(event)
388
+ end
389
+ end
390
+ end
391
+ end
392
+ @listener_classes[listener_class_key]
393
+ end
394
+
395
+ def add_swt_event_listener(swt_constant, &block)
396
+ event_type = SWTProxy[swt_constant]
397
+ widget_listener_proxy = nil
398
+ safe_block = lambda { |event| block.call(event) unless @swt_widget.isDisposed }
399
+ @swt_widget.addListener(event_type, &safe_block)
400
+ widget_listener_proxy = WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: @swt_widget.getListeners(event_type).last, event_type: event_type, swt_constant: swt_constant)
401
+ end
402
+
403
+ def widget_custom_attribute_mapping
404
+ @swt_widget_custom_attribute_mapping ||= {
405
+ 'focus' => {
406
+ getter: {name: 'isFocusControl'},
407
+ setter: {name: 'setFocus', invoker: lambda { |widget, args| @swt_widget.setFocus if args.first }},
408
+ },
409
+ 'caret_position' => {
410
+ getter: {name: 'getCaretPosition'},
411
+ setter: {name: 'setSelection', invoker: lambda { |widget, args| @swt_widget.setSelection(args.first) if args.first }},
412
+ },
413
+ 'selection_count' => {
414
+ getter: {name: 'getSelectionCount'},
415
+ setter: {name: 'setSelection', invoker: lambda { |widget, args| @swt_widget.setSelection(@swt_widget.getCaretPosition, @swt_widget.getCaretPosition + args.first) if args.first }},
416
+ },
417
+ }
418
+ end
419
+
420
+ def apply_property_type_converters(attribute_name, args)
421
+ if args.count == 1
422
+ value = args.first
423
+ converter = property_type_converters[attribute_name.to_sym]
424
+ args[0] = converter.call(value) if converter
425
+ end
426
+ if args.count == 1 && args.first.is_a?(ColorProxy)
427
+ g_color = args.first
428
+ args[0] = g_color.swt_color
429
+ end
430
+ end
431
+
432
+ def property_type_converters
433
+ color_converter = lambda do |value|
434
+ if value.is_a?(Symbol) || value.is_a?(String)
435
+ ColorProxy.new(value).swt_color
436
+ else
437
+ value
438
+ end
439
+ end
440
+ # TODO consider detecting type on widget method and automatically invoking right converter (e.g. :to_s for String, :to_i for Integer)
441
+ @property_type_converters ||= {
442
+ :background => color_converter,
443
+ :background_image => lambda do |value|
444
+ if value.is_a?(String)
445
+ if value.start_with?('uri:classloader')
446
+ value = value.sub(/^uri\:classloader\:\//, '')
447
+ object = java.lang.Object.new
448
+ value = object.java_class.resource_as_stream(value)
449
+ value = java.io.BufferedInputStream.new(value)
450
+ end
451
+ image_data = ImageData.new(value)
452
+ # TODO in the future, look into unregistering this listener when no longer needed
453
+ on_event_Resize do |resize_event|
454
+ new_image_data = image_data.scaledTo(@swt_widget.getSize.x, @swt_widget.getSize.y)
455
+ @swt_widget.getBackgroundImage&.dispose
456
+ @swt_widget.setBackgroundImage(Image.new(@swt_widget.getDisplay, new_image_data))
457
+ end
458
+ Image.new(@swt_widget.getDisplay, image_data)
459
+ else
460
+ value
461
+ end
462
+ end,
463
+ :foreground => color_converter,
464
+ :font => lambda do |value|
465
+ if value.is_a?(Hash)
466
+ font_properties = value
467
+ FontProxy.new(self, font_properties).swt_font
468
+ else
469
+ value
470
+ end
471
+ end,
472
+ :items => lambda do |value|
473
+ value.to_java :string
474
+ end,
475
+ :text => lambda do |value|
476
+ if swt_widget.is_a?(Browser)
477
+ value.to_s
478
+ else
479
+ value.to_s
480
+ end
481
+ end,
482
+ :visible => lambda do |value|
483
+ !!value
484
+ end,
485
+ }
486
+ end
487
+ end
488
+ end
489
+ end