glimmer-dsl-swt 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.
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