glimmer-dsl-swt 4.18.4.4 → 4.18.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,166 +1,167 @@
1
- # Copyright (c) 2007-2021 Andy Maleh
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining
4
- # a copy of this software and associated documentation files (the
5
- # "Software"), to deal in the Software without restriction, including
6
- # without limitation the rights to use, copy, modify, merge, publish,
7
- # distribute, sublicense, and/or sell copies of the Software, and to
8
- # permit persons to whom the Software is furnished to do so, subject to
9
- # the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be
12
- # included in all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
-
22
- require 'glimmer/swt/widget_listener_proxy'
23
- require 'glimmer/swt/custom/drawable'
24
-
25
- module Glimmer
26
- module SWT
27
- # Proxy for org.eclipse.swt.widgets.Display
28
- #
29
- # Maintains a singleton instance since SWT only supports
30
- # a single active display at a time.
31
- #
32
- # Supports SWT Display's very useful asyncExec and syncExec methods
33
- # to support proper multi-threaded manipulation of SWT UI objects
34
- #
35
- # Invoking `#swt_display` returns the SWT Display object wrapped by this proxy
36
- #
37
- # Follows the Proxy Design Pattern
38
- class DisplayProxy
39
- include_package 'org.eclipse.swt.widgets'
40
-
41
- include Custom::Drawable
42
-
43
- OBSERVED_MENU_ITEMS = ['about', 'preferences', 'quit']
44
-
45
- class FilterListener
46
- include org.eclipse.swt.widgets.Listener
47
-
48
- def initialize(&listener_block)
49
- @listener_block = listener_block
50
- end
51
-
52
- def handleEvent(event)
53
- @listener_block.call(event)
54
- end
55
- end
56
-
57
- class << self
58
- # Returns singleton instance
59
- def instance(*args)
60
- if @instance.nil? || @instance.swt_display.nil? || @instance.swt_display.isDisposed
61
- @instance = new(*args)
62
- end
63
- @instance
64
- end
65
- end
66
-
67
- # SWT Display object wrapped
68
- attr_reader :swt_display
69
-
70
- def initialize(*args)
71
- Display.app_name ||= 'Glimmer'
72
- @swt_display = Display.new(*args)
73
- @swt_display.set_data('proxy', self)
74
- on_swt_Dispose {
75
- clear_shapes
76
- }
77
- end
78
-
79
- def content(&block)
80
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::DisplayExpression.new, &block)
81
- end
82
-
83
- def async_exec(&block)
84
- @swt_display.async_exec(&block)
85
- end
86
-
87
- def sync_exec(&block)
88
- @swt_display.sync_exec(&block)
89
- end
90
-
91
- def timer_exec(&block)
92
- @swt_display.timer_exec(&block)
93
- end
94
-
95
- def on_widget_disposed(&block)
96
- on_swt_Dispose(&block)
97
- end
98
-
99
- def disposed?
100
- @swt_display.isDisposed
101
- end
102
-
103
- def method_missing(method, *args, &block)
104
- if can_handle_observation_request?(method)
105
- handle_observation_request(method, &block)
106
- else
107
- swt_display.send(method, *args, &block)
108
- end
109
- rescue => e
110
- Glimmer::Config.logger.debug {"Neither DisplayProxy nor #{swt_display.class.name} can handle the method ##{method}"}
111
- super
112
- end
113
-
114
- def respond_to?(method, *args, &block)
115
- super ||
116
- can_handle_observation_request?(method) ||
117
- swt_display.respond_to?(method, *args, &block)
118
- end
119
-
120
- def can_handle_observation_request?(observation_request)
121
- observation_request = observation_request.to_s
122
- if observation_request.start_with?('on_swt_')
123
- constant_name = observation_request.sub(/^on_swt_/, '')
124
- SWTProxy.has_constant?(constant_name)
125
- elsif observation_request.start_with?('on_')
126
- event_name = observation_request.sub(/^on_/, '')
127
- OBSERVED_MENU_ITEMS.include?(event_name)
128
- else
129
- false
130
- end
131
- end
132
-
133
- def handle_observation_request(observation_request, &block)
134
- observation_request = observation_request.to_s
135
- if observation_request.start_with?('on_swt_')
136
- constant_name = observation_request.sub(/^on_swt_/, '')
137
- add_swt_event_filter(constant_name, &block)
138
- elsif observation_request.start_with?('on_')
139
- event_name = observation_request.sub(/^on_/, '')
140
- if OBSERVED_MENU_ITEMS.include?(event_name)
141
- if OS.mac?
142
- system_menu = swt_display.getSystemMenu
143
- menu_item = system_menu.getItems.find {|menu_item| menu_item.getID == SWTProxy["ID_#{event_name.upcase}"]}
144
- menu_item.addListener(SWTProxy[:Selection], &block)
145
- end
146
- end
147
- end
148
- end
149
-
150
- def add_swt_event_filter(swt_constant, &block)
151
- event_type = SWTProxy[swt_constant]
152
- @swt_display.addFilter(event_type, FilterListener.new(&block))
153
- #WidgetListenerProxy.new(@swt_display.getListeners(event_type).last)
154
- WidgetListenerProxy.new(
155
- swt_display: @swt_display,
156
- event_type: event_type,
157
- filter: true,
158
- swt_listener: block,
159
- widget_add_listener_method: 'addFilter',
160
- swt_listener_class: FilterListener,
161
- swt_listener_method: 'handleEvent'
162
- )
163
- end
164
- end
165
- end
166
- end
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/swt/widget_listener_proxy'
23
+ require 'glimmer/swt/custom/drawable'
24
+
25
+ module Glimmer
26
+ module SWT
27
+ # Proxy for org.eclipse.swt.widgets.Display
28
+ #
29
+ # Maintains a singleton instance since SWT only supports
30
+ # a single active display at a time.
31
+ #
32
+ # Supports SWT Display's very useful asyncExec and syncExec methods
33
+ # to support proper multi-threaded manipulation of SWT UI objects
34
+ #
35
+ # Invoking `#swt_display` returns the SWT Display object wrapped by this proxy
36
+ #
37
+ # Follows the Proxy Design Pattern
38
+ class DisplayProxy
39
+ include_package 'org.eclipse.swt.widgets'
40
+
41
+ include Custom::Drawable
42
+
43
+ OBSERVED_MENU_ITEMS = ['about', 'preferences', 'quit']
44
+
45
+ class FilterListener
46
+ include org.eclipse.swt.widgets.Listener
47
+
48
+ def initialize(&listener_block)
49
+ @listener_block = listener_block
50
+ end
51
+
52
+ def handleEvent(event)
53
+ @listener_block.call(event)
54
+ end
55
+ end
56
+
57
+ class << self
58
+ # Returns singleton instance
59
+ def instance(*args)
60
+ if @instance.nil? || @instance.swt_display.nil? || @instance.swt_display.isDisposed
61
+ @instance = new(*args)
62
+ end
63
+ @instance
64
+ end
65
+ end
66
+
67
+ # SWT Display object wrapped
68
+ attr_reader :swt_display
69
+
70
+ def initialize(*args)
71
+ Display.app_name ||= 'Glimmer'
72
+ @swt_display = Display.new(*args)
73
+ @swt_display.set_data('proxy', self)
74
+ on_swt_Dispose {
75
+ clear_shapes
76
+ }
77
+ end
78
+
79
+ def content(&block)
80
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::DisplayExpression.new, &block)
81
+ end
82
+
83
+ def async_exec(&block)
84
+ @swt_display.asyncExec(&block)
85
+ end
86
+
87
+ def sync_exec(&block)
88
+ @swt_display.syncExec(&block)
89
+ end
90
+
91
+ def timer_exec(delay_in_millis, &block)
92
+ @swt_display.timerExec(delay_in_millis, &block)
93
+ end
94
+
95
+ def on_widget_disposed(&block)
96
+ on_swt_Dispose(&block)
97
+ end
98
+
99
+ def disposed?
100
+ @swt_display.isDisposed
101
+ end
102
+
103
+ def method_missing(method, *args, &block)
104
+ if can_handle_observation_request?(method)
105
+ handle_observation_request(method, &block)
106
+ else
107
+ swt_display.send(method, *args, &block)
108
+ end
109
+ rescue => e
110
+ Glimmer::Config.logger.debug {"Neither DisplayProxy nor #{swt_display.class.name} can handle the method ##{method}"}
111
+ super
112
+ end
113
+
114
+ def respond_to?(method, *args, &block)
115
+ super ||
116
+ can_handle_observation_request?(method) ||
117
+ swt_display.respond_to?(method, *args, &block)
118
+ end
119
+
120
+ def can_handle_observation_request?(observation_request)
121
+ observation_request = observation_request.to_s
122
+ if observation_request.start_with?('on_swt_')
123
+ constant_name = observation_request.sub(/^on_swt_/, '')
124
+ SWTProxy.has_constant?(constant_name)
125
+ elsif observation_request.start_with?('on_')
126
+ event_name = observation_request.sub(/^on_/, '')
127
+ OBSERVED_MENU_ITEMS.include?(event_name)
128
+ else
129
+ false
130
+ end
131
+ end
132
+
133
+ def handle_observation_request(observation_request, &block)
134
+ observation_request = observation_request.to_s
135
+ if observation_request.start_with?('on_swt_')
136
+ constant_name = observation_request.sub(/^on_swt_/, '')
137
+ add_swt_event_filter(constant_name, &block)
138
+ elsif observation_request.start_with?('on_')
139
+ event_name = observation_request.sub(/^on_/, '')
140
+ if OBSERVED_MENU_ITEMS.include?(event_name) && OS.mac?
141
+ system_menu = swt_display.getSystemMenu
142
+ menu_item = system_menu.getItems.find {|menu_item| menu_item.getID == SWTProxy["ID_#{event_name.upcase}"]}
143
+ display_mac_event_registration = menu_item.addListener(SWTProxy[:Selection], &block)
144
+ # TODO enable this code and test on the Mac to ensure automatic cleanup of mac event registrations in custom widgets
145
+ # Glimmer::UI::CustomWidget.current_custom_widgets.last&.observer_registrations&.push(display_mac_event_registration)
146
+ display_mac_event_registration
147
+ end
148
+ end
149
+ end
150
+
151
+ def add_swt_event_filter(swt_constant, &block)
152
+ event_type = SWTProxy[swt_constant]
153
+ @swt_display.addFilter(event_type, FilterListener.new(&block))
154
+ #WidgetListenerProxy.new(@swt_display.getListeners(event_type).last)
155
+ WidgetListenerProxy.new(
156
+ swt_display: @swt_display,
157
+ event_type: event_type,
158
+ filter: true,
159
+ swt_listener: block,
160
+ widget_add_listener_method: 'addFilter',
161
+ swt_listener_class: FilterListener,
162
+ swt_listener_method: 'handleEvent'
163
+ )
164
+ end
165
+ end
166
+ end
167
+ end
@@ -1,957 +1,964 @@
1
- # Copyright (c) 2007-2021 Andy Maleh
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining
4
- # a copy of this software and associated documentation files (the
5
- # "Software"), to deal in the Software without restriction, including
6
- # without limitation the rights to use, copy, modify, merge, publish,
7
- # distribute, sublicense, and/or sell copies of the Software, and to
8
- # permit persons to whom the Software is furnished to do so, subject to
9
- # the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be
12
- # included in all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
-
22
- require 'glimmer/swt/widget_listener_proxy'
23
- require 'glimmer/swt/color_proxy'
24
- require 'glimmer/swt/font_proxy'
25
- require 'glimmer/swt/swt_proxy'
26
- require 'glimmer/swt/display_proxy'
27
- require 'glimmer/swt/dnd_proxy'
28
- require 'glimmer/swt/image_proxy'
29
- require 'glimmer/swt/properties'
30
- require 'glimmer/swt/custom/drawable'
31
-
32
- # TODO refactor to make file smaller and extract sub-widget-proxies out of this
33
-
34
- module Glimmer
35
- module SWT
36
- # Proxy for SWT Widget objects
37
- #
38
- # Sets default SWT styles to widgets upon inititalizing as
39
- # per DEFAULT_STYLES
40
- #
41
- # Also, auto-initializes widgets as per initializer blocks
42
- # in DEFAULT_INITIALIZERS (e.g. setting Composite default layout)
43
- #
44
- # Follows the Proxy Design Pattern
45
- class WidgetProxy
46
- include Packages
47
- include Properties
48
- include Custom::Drawable
49
-
50
- DEFAULT_STYLES = {
51
- 'arrow' => [:arrow],
52
- 'button' => [:push],
53
- 'checkbox' => [:check],
54
- 'check' => [:check],
55
- 'drag_source' => [:drop_copy],
56
- 'drop_target' => [:drop_copy],
57
- 'expand_bar' => [:v_scroll],
58
- 'list' => [:border, :v_scroll],
59
- 'menu_item' => [:push],
60
- 'radio' => [:radio],
61
- 'scrolled_composite' => [:border, :h_scroll, :v_scroll],
62
- 'spinner' => [:border],
63
- 'styled_text' => [:border, :multi, :v_scroll, :h_scroll],
64
- 'table' => [:virtual, :border, :full_selection],
65
- 'text' => [:border],
66
- 'toggle' => [:toggle],
67
- 'tool_bar' => [:push],
68
- 'tool_item' => [:push],
69
- 'tree' => [:virtual, :border, :h_scroll, :v_scroll],
70
- 'date_drop_down' => [:date, :drop_down],
71
- 'time' => [:time],
72
- 'calendar' => [:calendar],
73
- }
74
-
75
- DEFAULT_INITIALIZERS = {
76
- composite: lambda do |composite|
77
- composite.layout = GridLayout.new if composite.get_layout.nil?
78
- end,
79
- canvas: lambda do |canvas|
80
- canvas.layout = nil if canvas.respond_to?('layout=') && !canvas.get_layout.nil?
81
- end,
82
- scrolled_composite: lambda do |scrolled_composite|
83
- scrolled_composite.expand_horizontal = true
84
- scrolled_composite.expand_vertical = true
85
- end,
86
- table: lambda do |table|
87
- table.setHeaderVisible(true)
88
- table.setLinesVisible(true)
89
- end,
90
- table_column: lambda do |table_column|
91
- table_column.setWidth(80)
92
- end,
93
- group: lambda do |group|
94
- group.layout = GridLayout.new if group.get_layout.nil?
95
- end,
96
- }
97
-
98
- KEYWORD_ALIASES = {
99
- 'arrow' => 'button',
100
- 'checkbox' => 'button',
101
- 'check' => 'button',
102
- 'radio' => 'button',
103
- 'toggle' => 'button',
104
- 'date' => 'date_time',
105
- 'date_drop_down' => 'date_time',
106
- 'time' => 'date_time',
107
- 'calendar' => 'date_time',
108
- }
109
-
110
- class << self
111
- # Instantiates the right WidgetProxy subclass for passed in keyword
112
- # Args are: keyword, parent, swt_widget_args (including styles)
113
- def create(*init_args, swt_widget: nil)
114
- return swt_widget.get_data('proxy') if swt_widget&.get_data('proxy')
115
- keyword, parent, args = init_args
116
- selected_widget_proxy_class = widget_proxy_class(keyword || underscored_widget_name(swt_widget))
117
- if init_args.empty?
118
- selected_widget_proxy_class.new(swt_widget: swt_widget)
119
- else
120
- selected_widget_proxy_class.new(*init_args)
121
- end
122
- end
123
-
124
- def widget_proxy_class(keyword)
125
- begin
126
- keyword = KEYWORD_ALIASES[keyword] if KEYWORD_ALIASES[keyword]
127
- class_name = "#{keyword.camelcase(:upper)}Proxy".to_sym
128
- Glimmer::SWT.const_get(class_name)
129
- rescue
130
- Glimmer::SWT::WidgetProxy
131
- end
132
- end
133
-
134
- def underscored_widget_name(swt_widget)
135
- swt_widget.class.name.split(/::|\./).last.underscore
136
- end
137
- end
138
-
139
- attr_reader :parent_proxy, :swt_widget, :drag_source_proxy, :drop_target_proxy, :drag_source_style, :drag_source_transfer, :drop_target_transfer
140
-
141
- # Initializes a new SWT Widget
142
- #
143
- # It is preferred to use `::create` method instead since it instantiates the
144
- # right subclass per widget keyword
145
- #
146
- # keyword, parent, swt_widget_args (including styles)
147
- #
148
- # Styles is a comma separate list of symbols representing SWT styles in lower case
149
- def initialize(*init_args, swt_widget: nil)
150
- if swt_widget.nil?
151
- underscored_widget_name, parent, args = init_args
152
- @parent_proxy = parent
153
- styles, extra_options = extract_args(underscored_widget_name, args)
154
- swt_widget_class = self.class.swt_widget_class_for(underscored_widget_name)
155
- @swt_widget = swt_widget_class.new(@parent_proxy.swt_widget, style(underscored_widget_name, styles), *extra_options)
156
- else
157
- @swt_widget = swt_widget
158
- underscored_widget_name = self.class.underscored_widget_name(@swt_widget)
159
- parent_proxy_class = self.class.widget_proxy_class(self.class.underscored_widget_name(@swt_widget.parent))
160
- parent = swt_widget.parent
161
- @parent_proxy = parent&.get_data('proxy') || parent_proxy_class.new(swt_widget: parent)
162
- end
163
- if @swt_widget&.get_data('proxy').nil?
164
- @swt_widget.set_data('proxy', self)
165
- DEFAULT_INITIALIZERS[underscored_widget_name.to_s.to_sym]&.call(@swt_widget)
166
- @parent_proxy.post_initialize_child(self)
167
- end
168
- @keyword = underscored_widget_name.to_s
169
- if respond_to?(:on_widget_disposed)
170
- on_widget_disposed {
171
- clear_shapes
172
- }
173
- end
174
- end
175
-
176
- # Subclasses may override to perform post initialization work on an added child
177
- def post_initialize_child(child)
178
- # No Op by default
179
- end
180
-
181
- # Subclasses may override to perform post add_content work
182
- def post_add_content
183
- # No Op by default
184
- end
185
-
186
- def extract_args(underscored_widget_name, args)
187
- @arg_extractor_mapping ||= {
188
- 'menu_item' => lambda do |args|
189
- index = args.delete(args.last) if args.last.is_a?(Numeric)
190
- extra_options = [index].compact
191
- styles = args
192
- [styles, extra_options]
193
- end,
194
- }
195
- if @arg_extractor_mapping[underscored_widget_name]
196
- @arg_extractor_mapping[underscored_widget_name].call(args)
197
- else
198
- extra_options = []
199
- style_args = args.select {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
200
- if style_args.any?
201
- style_arg_start_index = args.index(style_args.first)
202
- style_arg_last_index = args.index(style_args.last)
203
- extra_options = args[style_arg_last_index+1..-1]
204
- args = args[style_arg_start_index..style_arg_last_index]
205
- elsif args.first.is_a?(Integer)
206
- extra_options = args[1..-1]
207
- args = args[0..0]
208
- end
209
- [args, extra_options]
210
- end
211
- end
212
-
213
- def has_attribute_getter?(attribute_getter_name, *args)
214
- attribute_getter_name = attribute_getter_name.to_s.underscore
215
- return false unless !attribute_getter_name.end_with?('=') && !attribute_getter_name.start_with?('set_')
216
- args.empty? && swt_widget.respond_to?(attribute_getter_name)
217
- end
218
-
219
- def has_attribute_setter?(attribute_setter_name, *args)
220
- attribute_setter_name = attribute_setter_name.to_s
221
- underscored_attribute_setter_name = attribute_setter_name.underscore
222
- return false unless attribute_setter_name.end_with?('=') || (attribute_setter_name.start_with?('set_') && !args.empty?)
223
- attribute_name = underscored_attribute_setter_name.sub(/^set_/, '').sub(/=$/, '')
224
- has_attribute?(attribute_name, *args)
225
- end
226
-
227
- def has_attribute?(attribute_name, *args)
228
- # TODO test that attribute getter responds too
229
- widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
230
- if widget_custom_attribute
231
- @swt_widget.respond_to?(widget_custom_attribute[:setter][:name])
232
- else
233
- @swt_widget.respond_to?(attribute_setter(attribute_name), args) || respond_to?(ruby_attribute_setter(attribute_name), args)
234
- end
235
- end
236
-
237
- def set_attribute(attribute_name, *args)
238
- # TODO Think about widget subclasses overriding set_attribute to add more attributes vs adding as Ruby attributes directly
239
- widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
240
- apply_property_type_converters(normalized_attribute(attribute_name), args)
241
- if widget_custom_attribute
242
- widget_custom_attribute[:setter][:invoker].call(@swt_widget, args)
243
- elsif @swt_widget.respond_to?(attribute_setter(attribute_name))
244
- @swt_widget.send(attribute_setter(attribute_name), *args) unless @swt_widget.send(attribute_getter(attribute_name)) == args.first
245
- elsif @swt_widget.respond_to?(ruby_attribute_setter(attribute_name))
246
- @swt_widget.send(ruby_attribute_setter(attribute_name), args)
247
- else
248
- send(ruby_attribute_setter(attribute_name), args)
249
- end
250
- end
251
-
252
- def get_attribute(attribute_name)
253
- widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
254
- if widget_custom_attribute
255
- if widget_custom_attribute[:getter][:invoker]
256
- widget_custom_attribute[:getter][:invoker].call(@swt_widget, [])
257
- else
258
- @swt_widget.send(widget_custom_attribute[:getter][:name])
259
- end
260
- elsif @swt_widget.respond_to?(attribute_getter(attribute_name))
261
- @swt_widget.send(attribute_getter(attribute_name))
262
- elsif @swt_widget.respond_to?(ruby_attribute_getter(attribute_name))
263
- @swt_widget.send(ruby_attribute_getter(attribute_name))
264
- elsif @swt_widget.respond_to?(attribute_name)
265
- @swt_widget.send(attribute_name)
266
- elsif respond_to?(ruby_attribute_getter(attribute_name))
267
- send(ruby_attribute_getter(attribute_name))
268
- else
269
- send(attribute_name)
270
- end
271
- end
272
-
273
- def pack_same_size
274
- bounds = @swt_widget.getBounds
275
- listener = on_control_resized {
276
- @swt_widget.setSize(bounds.width, bounds.height)
277
- @swt_widget.setLocation(bounds.x, bounds.y)
278
- }
279
- if @swt_widget.is_a?(Composite)
280
- @swt_widget.layout(true, true)
281
- else
282
- @swt_widget.pack(true)
283
- end
284
- @swt_widget.removeControlListener(listener.swt_listener)
285
- end
286
-
287
- def widget_property_listener_installers
288
- @swt_widget_property_listener_installers ||= {
289
- Java::OrgEclipseSwtWidgets::Control => {
290
- :focus => lambda do |observer|
291
- on_focus_gained { |focus_event|
292
- observer.call(true)
293
- }
294
- on_focus_lost { |focus_event|
295
- observer.call(false)
296
- }
297
- end,
298
- :selection => lambda do |observer|
299
- on_widget_selected { |selection_event|
300
- observer.call(@swt_widget.getSelection)
301
- } if can_handle_observation_request?(:on_widget_selected)
302
- end,
303
- :text => lambda do |observer|
304
- on_modify_text { |modify_event|
305
- observer.call(@swt_widget.getText)
306
- } if can_handle_observation_request?(:on_modify_text)
307
- end,
308
- },
309
- Java::OrgEclipseSwtWidgets::Combo => {
310
- :text => lambda do |observer|
311
- on_modify_text { |modify_event|
312
- observer.call(@swt_widget.getText)
313
- }
314
- end,
315
- },
316
- Java::OrgEclipseSwtWidgets::Table => {
317
- :selection => lambda do |observer|
318
- on_widget_selected { |selection_event|
319
- if has_style?(:multi)
320
- observer.call(@swt_widget.getSelection.map(&:get_data))
321
- else
322
- observer.call(@swt_widget.getSelection.first&.get_data)
323
- end
324
- }
325
- end,
326
- },
327
- Java::OrgEclipseSwtWidgets::Text => {
328
- :text => lambda do |observer|
329
- on_modify_text { |modify_event|
330
- observer.call(@swt_widget.getText)
331
- }
332
- end,
333
- :caret_position => lambda do |observer|
334
- on_swt_keydown { |event|
335
- observer.call(@swt_widget.getCaretPosition)
336
- }
337
- on_swt_keyup { |event|
338
- observer.call(@swt_widget.getCaretPosition)
339
- }
340
- on_swt_mousedown { |event|
341
- observer.call(@swt_widget.getCaretPosition)
342
- }
343
- on_swt_mouseup { |event|
344
- observer.call(@swt_widget.getCaretPosition)
345
- }
346
- end,
347
- :selection => lambda do |observer|
348
- on_swt_keydown { |event|
349
- observer.call(@swt_widget.getSelection)
350
- }
351
- on_swt_keyup { |event|
352
- observer.call(@swt_widget.getSelection)
353
- }
354
- on_swt_mousedown { |event|
355
- observer.call(@swt_widget.getSelection)
356
- }
357
- on_swt_mouseup { |event|
358
- observer.call(@swt_widget.getSelection)
359
- }
360
- end,
361
- :selection_count => lambda do |observer|
362
- on_swt_keydown { |event|
363
- observer.call(@swt_widget.getSelectionCount)
364
- }
365
- on_swt_keyup { |event|
366
- observer.call(@swt_widget.getSelectionCount)
367
- }
368
- on_swt_mousedown { |event|
369
- observer.call(@swt_widget.getSelectionCount)
370
- }
371
- on_swt_mouseup { |event|
372
- observer.call(@swt_widget.getSelectionCount)
373
- }
374
- end,
375
- :top_index => lambda do |observer|
376
- @last_top_index = @swt_widget.getTopIndex
377
- on_paint_control { |event|
378
- if @swt_widget.getTopIndex != @last_top_index
379
- @last_top_index = @swt_widget.getTopIndex
380
- observer.call(@last_top_index)
381
- end
382
- }
383
- end,
384
- },
385
- Java::OrgEclipseSwtCustom::StyledText => {
386
- :text => lambda do |observer|
387
- on_modify_text { |modify_event|
388
- observer.call(@swt_widget.getText)
389
- }
390
- end,
391
- :caret_position => lambda do |observer|
392
- on_caret_moved { |event|
393
- observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
394
- }
395
- on_swt_keyup { |event|
396
- observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
397
- }
398
- on_swt_mouseup { |event|
399
- observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
400
- }
401
- end,
402
- :caret_offset => lambda do |observer|
403
- on_caret_moved { |event|
404
- observer.call(@swt_widget.getCaretOffset) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
405
- }
406
- end,
407
- :selection => lambda do |observer|
408
- on_widget_selected { |event|
409
- observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
410
- }
411
- on_swt_keyup { |event|
412
- observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
413
- }
414
- on_swt_mouseup { |event|
415
- observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
416
- }
417
- end,
418
- :selection_count => lambda do |observer|
419
- on_widget_selected { |event|
420
- observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
421
- }
422
- on_swt_keyup { |event|
423
- observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
424
- }
425
- on_swt_mouseup { |event|
426
- observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
427
- }
428
- end,
429
- :selection_range => lambda do |observer|
430
- on_widget_selected { |event|
431
- observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
432
- }
433
- on_swt_keyup { |event|
434
- observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
435
- }
436
- on_swt_mouseup { |event|
437
- observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
438
- }
439
- end,
440
- :top_index => lambda do |observer|
441
- @last_top_index = @swt_widget.getTopIndex
442
- on_paint_control { |event|
443
- if @swt_widget.getTopIndex != @last_top_index
444
- @last_top_index = @swt_widget.getTopIndex
445
- observer.call(@last_top_index)
446
- end
447
- }
448
- end,
449
- :top_pixel => lambda do |observer|
450
- @last_top_pixel = @swt_widget.getTopPixel
451
- on_paint_control { |event|
452
- if @swt_widget.getTopPixel != @last_top_pixel
453
- @last_top_pixel = @swt_widget.getTopPixel
454
- observer.call(@last_top_pixel)
455
- end
456
- }
457
- end,
458
- },
459
- Java::OrgEclipseSwtWidgets::Button => {
460
- :selection => lambda do |observer|
461
- on_widget_selected { |selection_event|
462
- observer.call(@swt_widget.getSelection)
463
- }
464
- end
465
- },
466
- Java::OrgEclipseSwtWidgets::MenuItem => {
467
- :selection => lambda do |observer|
468
- on_widget_selected { |selection_event|
469
- observer.call(@swt_widget.getSelection)
470
- }
471
- end
472
- },
473
- Java::OrgEclipseSwtWidgets::Spinner => {
474
- :selection => lambda do |observer|
475
- on_widget_selected { |selection_event|
476
- observer.call(@swt_widget.getSelection)
477
- }
478
- end
479
- },
480
- Java::OrgEclipseSwtWidgets::DateTime =>
481
- [:year, :month, :day, :hours, :minutes, :seconds, :date_time, :date, :time].reduce({}) do |hash, attribute|
482
- hash.merge(
483
- attribute => lambda do |observer|
484
- on_widget_selected { |selection_event|
485
- observer.call(get_attribute(attribute))
486
- }
487
- end
488
- )
489
- end,
490
- }
491
- end
492
-
493
- def self.widget_exists?(underscored_widget_name)
494
- !!swt_widget_class_for(underscored_widget_name)
495
- end
496
-
497
- # Manual entries of SWT widget classes that conflict with Ruby classes
498
- def self.swt_widget_class_manual_entries
499
- {
500
- 'date_time' => Java::OrgEclipseSwtWidgets::DateTime
501
- }
502
- end
503
-
504
- # This supports widgets in and out of basic SWT
505
- def self.swt_widget_class_for(underscored_widget_name)
506
- # TODO clear memoization for a keyword if a custom widget was defined with that keyword
507
- unless flyweight_swt_widget_classes.keys.include?(underscored_widget_name)
508
- begin
509
- underscored_widget_name = KEYWORD_ALIASES[underscored_widget_name] if KEYWORD_ALIASES[underscored_widget_name]
510
- swt_widget_name = underscored_widget_name.camelcase(:upper)
511
- swt_widget_class = eval(swt_widget_name)
512
- # TODO fix issue with not detecting DateTime because it's conflicting with the Ruby DateTime
513
- unless swt_widget_class.ancestors.include?(org.eclipse.swt.widgets.Widget)
514
- swt_widget_class = swt_widget_class_manual_entries[underscored_widget_name]
515
- if swt_widget_class.nil?
516
- Glimmer::Config.logger.debug {"Class #{swt_widget_class} matching #{underscored_widget_name} is not a subclass of org.eclipse.swt.widgets.Widget"}
517
- return nil
518
- end
519
- end
520
- flyweight_swt_widget_classes[underscored_widget_name] = swt_widget_class
521
- rescue SyntaxError, NameError => e
522
- Glimmer::Config.logger.debug {e.full_message}
523
- # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
524
- nil
525
- rescue => e
526
- Glimmer::Config.logger.debug {e.full_message}
527
- # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
528
- nil
529
- end
530
- end
531
- flyweight_swt_widget_classes[underscored_widget_name]
532
- end
533
-
534
- # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
535
- def self.flyweight_swt_widget_classes
536
- @flyweight_swt_widget_classes ||= {}
537
- end
538
-
539
- def async_exec(&block)
540
- DisplayProxy.instance.async_exec(&block)
541
- end
542
-
543
- def sync_exec(&block)
544
- DisplayProxy.instance.sync_exec(&block)
545
- end
546
-
547
- def has_style?(style)
548
- comparison = interpret_style(style)
549
- (@swt_widget.style & comparison) == comparison
550
- end
551
-
552
- def dispose
553
- @swt_widget.dispose
554
- end
555
-
556
- def disposed?
557
- @swt_widget.isDisposed
558
- end
559
-
560
- # TODO Consider renaming these methods as they are mainly used for data-binding
561
-
562
- def can_add_observer?(property_name)
563
- @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact.map(&:keys).flatten.map(&:to_s).include?(property_name.to_s)
564
- end
565
-
566
- # Used for data-binding only. Consider renaming or improving to avoid the confusion it causes
567
- def add_observer(observer, property_name)
568
- if !observer.respond_to?(:binding_options) || !observer.binding_options[:read_only]
569
- property_listener_installers = @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
570
- widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
571
- widget_listener_installers.to_a.first&.call(observer)
572
- end
573
- end
574
-
575
- def remove_observer(observer, property_name)
576
- # TODO consider implementing if remove_observer is needed (consumers can remove listener via SWT API)
577
- end
578
-
579
- def ensure_drag_source_proxy(style=[])
580
- @drag_source_proxy ||= self.class.new('drag_source', self, style).tap do |proxy|
581
- proxy.set_attribute(:transfer, :text)
582
- end
583
- end
584
-
585
- def ensure_drop_target_proxy(style=[])
586
- @drop_target_proxy ||= self.class.new('drop_target', self, style).tap do |proxy|
587
- proxy.set_attribute(:transfer, :text)
588
- proxy.on_drag_enter { |event|
589
- event.detail = DNDProxy[:drop_copy]
590
- }
591
- end
592
- end
593
-
594
- # TODO eliminate duplication in the following methods perhaps by relying on exceptions
595
-
596
- def can_handle_observation_request?(observation_request)
597
- observation_request = observation_request.to_s
598
- if observation_request.start_with?('on_swt_')
599
- constant_name = observation_request.sub(/^on_swt_/, '')
600
- SWTProxy.has_constant?(constant_name)
601
- elsif observation_request.start_with?('on_')
602
- event = observation_request.sub(/^on_/, '')
603
- can_add_listener?(event) || can_handle_drag_observation_request?(observation_request) || can_handle_drop_observation_request?(observation_request)
604
- end
605
- end
606
-
607
- def can_handle_drag_observation_request?(observation_request)
608
- return false unless swt_widget.is_a?(Control)
609
- potential_drag_source = @drag_source_proxy.nil?
610
- ensure_drag_source_proxy
611
- @drag_source_proxy.can_handle_observation_request?(observation_request).tap do |result|
612
- if potential_drag_source && !result
613
- @drag_source_proxy.swt_widget.dispose
614
- @drag_source_proxy = nil
615
- end
616
- end
617
- rescue => e
618
- Glimmer::Config.logger.debug {e.full_message}
619
- false
620
- end
621
-
622
- def can_handle_drop_observation_request?(observation_request)
623
- return false unless swt_widget.is_a?(Control)
624
- potential_drop_target = @drop_target_proxy.nil?
625
- ensure_drop_target_proxy
626
- @drop_target_proxy.can_handle_observation_request?(observation_request).tap do |result|
627
- if potential_drop_target && !result
628
- @drop_target_proxy.swt_widget.dispose
629
- @drop_target_proxy = nil
630
- end
631
- end
632
- end
633
-
634
- def handle_observation_request(observation_request, &block)
635
- observation_request = observation_request.to_s
636
- if observation_request.start_with?('on_swt_')
637
- constant_name = observation_request.sub(/^on_swt_/, '')
638
- add_swt_event_listener(constant_name, &block)
639
- elsif observation_request.start_with?('on_')
640
- event = observation_request.sub(/^on_/, '')
641
- if can_add_listener?(event)
642
- event = observation_request.sub(/^on_/, '')
643
- add_listener(event, &block)
644
- elsif can_handle_drag_observation_request?(observation_request)
645
- @drag_source_proxy&.handle_observation_request(observation_request, &block)
646
- elsif can_handle_drop_observation_request?(observation_request)
647
- @drop_target_proxy&.handle_observation_request(observation_request, &block)
648
- end
649
- end
650
- end
651
-
652
- def content(&block)
653
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, &block)
654
- end
655
-
656
- def method_missing(method, *args, &block)
657
- if can_handle_observation_request?(method)
658
- handle_observation_request(method, &block)
659
- elsif has_attribute_setter?(method, *args)
660
- set_attribute(method, *args)
661
- elsif has_attribute_getter?(method, *args)
662
- get_attribute(method, *args)
663
- else
664
- swt_widget.send(method, *args, &block)
665
- end
666
- rescue => e
667
- Glimmer::Config.logger.debug { "Neither WidgetProxy nor #{swt_widget.class.name} can handle the method ##{method}" }
668
- Glimmer::Config.logger.debug { e.full_message }
669
- super
670
- # TODO consider get_attribute too
671
- end
672
-
673
- def respond_to?(method, *args, &block)
674
- super ||
675
- can_handle_observation_request?(method) ||
676
- swt_widget.respond_to?(method, *args, &block)
677
- end
678
-
679
- private
680
-
681
- def style(underscored_widget_name, styles)
682
- styles = [styles].flatten.compact
683
- styles = default_style(underscored_widget_name) if styles.empty?
684
- interpret_style(*styles)
685
- end
686
-
687
- def interpret_style(*styles)
688
- SWTProxy[*styles] rescue DNDProxy[*styles]
689
- end
690
-
691
- def default_style(underscored_widget_name)
692
- DEFAULT_STYLES[underscored_widget_name] || [:none]
693
- end
694
-
695
- # TODO refactor following methods to eliminate duplication
696
- # perhaps consider relying on raising an exception to avoid checking first
697
- # unless that gives obscure SWT errors
698
- # Otherwise, consider caching results from can_add_lsitener and using them in
699
- # add_listener knowing it will be called for sure afterwards
700
-
701
- def can_add_listener?(underscored_listener_name)
702
- !self.class.find_listener(@swt_widget.getClass, underscored_listener_name).empty?
703
- end
704
-
705
- def add_listener(underscored_listener_name, &block)
706
- widget_add_listener_method, listener_class, listener_method = self.class.find_listener(@swt_widget.getClass, underscored_listener_name)
707
- widget_listener_proxy = nil
708
- safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
709
- listener = listener_class.new(listener_method => safe_block)
710
- @swt_widget.send(widget_add_listener_method, listener)
711
- 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)
712
- end
713
-
714
- # Looks through SWT class add***Listener methods till it finds one for which
715
- # the argument is a listener class that has an event method matching
716
- # underscored_listener_name
717
- def self.find_listener(swt_widget_class, underscored_listener_name)
718
- @listeners ||= {}
719
- listener_key = [swt_widget_class.name, underscored_listener_name]
720
- unless @listeners.has_key?(listener_key)
721
- listener_method_name = underscored_listener_name.camelcase(:lower)
722
- swt_widget_class.getMethods.each do |widget_add_listener_method|
723
- if widget_add_listener_method.getName.match(/add.*Listener/)
724
- widget_add_listener_method.getParameterTypes.each do |listener_type|
725
- listener_type.getMethods.each do |listener_method|
726
- if (listener_method.getName == listener_method_name)
727
- @listeners[listener_key] = [widget_add_listener_method.getName, listener_class(listener_type), listener_method.getName]
728
- return @listeners[listener_key]
729
- end
730
- end
731
- end
732
- end
733
- end
734
- @listeners[listener_key] = []
735
- end
736
- @listeners[listener_key]
737
- end
738
-
739
- # Returns a Ruby class that implements listener type Java interface with ability to easily
740
- # install a block that gets called upon calling a listener event method
741
- def self.listener_class(listener_type)
742
- @listener_classes ||= {}
743
- listener_class_key = listener_type.name
744
- unless @listener_classes.has_key?(listener_class_key)
745
- @listener_classes[listener_class_key] = Class.new(Object).tap do |listener_class|
746
- listener_class.send :include, (eval listener_type.name.sub("interface", ""))
747
- listener_class.define_method('initialize') do |event_method_block_mapping|
748
- @event_method_block_mapping = event_method_block_mapping
749
- end
750
- listener_type.getMethods.each do |event_method|
751
- listener_class.define_method(event_method.getName) do |*args|
752
- @event_method_block_mapping[event_method.getName]&.call(*args)
753
- end
754
- end
755
- end
756
- end
757
- @listener_classes[listener_class_key]
758
- end
759
-
760
- def add_swt_event_listener(swt_constant, &block)
761
- event_type = SWTProxy[swt_constant]
762
- widget_listener_proxy = nil
763
- safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
764
- @swt_widget.addListener(event_type, &safe_block)
765
- 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)
766
- end
767
-
768
- def widget_custom_attribute_mapping
769
- # TODO scope per widget class type just like other mappings
770
- @swt_widget_custom_attribute_mapping ||= {
771
- 'focus' => {
772
- getter: {name: 'isFocusControl'},
773
- setter: {name: 'setFocus', invoker: lambda { |widget, args| @swt_widget.setFocus if args.first }},
774
- },
775
- 'caret_position' => {
776
- getter: {name: 'getCaretPosition', invoker: lambda { |widget, args| @swt_widget.respond_to?(:getCaretPosition) ? @swt_widget.getCaretPosition : @swt_widget.getSelection.x}},
777
- setter: {name: 'setSelection', invoker: lambda { |widget, args| @swt_widget.setSelection(args.first, args.first + @swt_widget.getSelectionCount) if args.first }},
778
- },
779
- 'selection_count' => {
780
- getter: {name: 'getSelectionCount'},
781
- setter: {name: 'setSelection', invoker: lambda { |widget, args|
782
- if args.first
783
- caret_position = @swt_widget.respond_to?(:getCaretPosition) ? @swt_widget.getCaretPosition : @swt_widget.getSelection.x
784
- # TODO handle negative length
785
- @swt_widget.setSelection(caret_position, caret_position + args.first)
786
- end
787
- }},
788
- },
789
- }
790
- end
791
-
792
- def drag_source_style=(style)
793
- ensure_drag_source_proxy(style)
794
- end
795
-
796
- def drop_target_style=(style)
797
- ensure_drop_target_proxy(style)
798
- end
799
-
800
- def drag_source_transfer=(args)
801
- args = args.first if !args.empty? && args.first.is_a?(ArrayJavaProxy)
802
- ensure_drag_source_proxy
803
- @drag_source_proxy.set_attribute(:transfer, args)
804
- end
805
-
806
- def drop_target_transfer=(args)
807
- args = args.first if !args.empty? && args.first.is_a?(ArrayJavaProxy)
808
- ensure_drop_target_proxy
809
- @drop_target_proxy.set_attribute(:transfer, args)
810
- end
811
-
812
- def drag_source_effect=(args)
813
- args = args.first if args.is_a?(Array)
814
- ensure_drag_source_proxy
815
- @drag_source_proxy.set_attribute(:drag_source_effect, args)
816
- end
817
-
818
- def drop_target_effect=(args)
819
- args = args.first if args.is_a?(Array)
820
- ensure_drop_target_proxy
821
- @drop_target_proxy.set_attribute(:drop_target_effect, args)
822
- end
823
-
824
- def apply_property_type_converters(attribute_name, args)
825
- value = args
826
- converter = property_type_converters[attribute_name.to_sym]
827
- args[0..-1] = [converter.call(*value)] if converter
828
- if args.count == 1 && args.first.is_a?(ColorProxy)
829
- g_color = args.first
830
- args[0] = g_color.swt_color
831
- end
832
- end
833
-
834
- def property_type_converters
835
- color_converter = lambda do |value|
836
- if value.is_a?(Symbol) || value.is_a?(String)
837
- ColorProxy.new(value).swt_color
838
- else
839
- value
840
- end
841
- end
842
- # TODO consider detecting type on widget method and automatically invoking right converter (e.g. :to_s for String, :to_i for Integer)
843
- @property_type_converters ||= {
844
- accelerator: lambda { |*value|
845
- SWTProxy[*value]
846
- },
847
- alignment: lambda { |*value|
848
- SWTProxy[*value]
849
- },
850
- background: color_converter,
851
- background_image: lambda do |*value|
852
- image_proxy = ImageProxy.create(*value)
853
-
854
- if image_proxy&.file_path&.end_with?('.gif')
855
- image = image_proxy.swt_image
856
- width = image.get_bounds.width.to_i
857
- height = image.get_bounds.height.to_i
858
- image_number = 0
859
- loader = ImageLoader.new
860
- loader.load(image_proxy.input_stream)
861
- image.dispose
862
- image = org.eclipse.swt.graphics.Image.new(DisplayProxy.instance.swt_display,loader.data[0].scaledTo(width, height))
863
- gc = org.eclipse.swt.graphics.GC.new(image)
864
- on_paint_control { |event|
865
- image_number = (image_number == loader.data.length - 1) ? 0 : image_number + 1
866
- next_frame_data = loader.data[image_number]
867
- image = org.eclipse.swt.graphics.Image.new(DisplayProxy.instance.swt_display, next_frame_data.scaledTo(width, height))
868
- event.gc.drawImage(image, 0, 0, width, height, 0, 0, width, height)
869
- image.dispose
870
- }
871
- Thread.new {
872
- last_image_number = -1
873
- while last_image_number != image_number
874
- last_image_number = image_number
875
- sync_exec {
876
- redraw
877
- }
878
- delayTime = loader.data[image_number].delayTime.to_f / 100.0
879
- sleep(delayTime)
880
- end
881
- };
882
- image_proxy = nil
883
- else
884
- on_swt_Resize do |resize_event|
885
- image_proxy.scale_to(@swt_widget.getSize.x, @swt_widget.getSize.y)
886
- @swt_widget.setBackgroundImage(image_proxy.swt_image)
887
- end
888
- end
889
-
890
- image_proxy&.swt_image
891
- end,
892
- cursor: lambda do |value|
893
- cursor_proxy = nil
894
- if value.is_a?(CursorProxy)
895
- cursor_proxy = value
896
- elsif value.is_a?(Symbol) || value.is_a?(String) || value.is_a?(Integer)
897
- cursor_proxy = CursorProxy.new(value)
898
- end
899
- cursor_proxy ? cursor_proxy.swt_cursor : value
900
- end,
901
- :enabled => lambda do |value|
902
- !!value
903
- end,
904
- foreground: color_converter,
905
- link_foreground: color_converter,
906
- font: lambda do |value|
907
- if value.is_a?(Hash)
908
- font_properties = value
909
- FontProxy.new(self, font_properties).swt_font
910
- else
911
- value
912
- end
913
- end,
914
- image: lambda do |*value|
915
- ImageProxy.create(*value).swt_image
916
- end,
917
- images: lambda do |array|
918
- array.to_a.map do |value|
919
- ImageProxy.create(value).swt_image
920
- end.to_java(Image)
921
- end,
922
- items: lambda do |value|
923
- value.to_java :string
924
- end,
925
- text: lambda do |value|
926
- value.to_s
927
- end,
928
- transfer: lambda do |value|
929
- value = value.first if value.is_a?(Array) && value.size == 1 && value.first.is_a?(Array)
930
- transfer_object_extrapolator = lambda do |transfer_name|
931
- transfer_type = "#{transfer_name.to_s.camelcase(:upper)}Transfer".to_sym
932
- transfer_type_alternative = "#{transfer_name.to_s.upcase}Transfer".to_sym
933
- transfer_class = org.eclipse.swt.dnd.const_get(transfer_type) rescue org.eclipse.swt.dnd.const_get(transfer_type_alternative)
934
- transfer_class.getInstance
935
- end
936
- result = value
937
- if value.is_a?(Symbol) || value.is_a?(String)
938
- result = [transfer_object_extrapolator.call(value)]
939
- elsif value.is_a?(Array)
940
- result = value.map do |transfer_name|
941
- transfer_object_extrapolator.call(transfer_name)
942
- end
943
- end
944
- result = result.to_java(Transfer) unless result.is_a?(ArrayJavaProxy)
945
- result
946
- end,
947
- visible: lambda do |value|
948
- !!value
949
- end,
950
- weights: lambda do |value|
951
- value.to_java(:int)
952
- end,
953
- }
954
- end
955
- end
956
- end
957
- end
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/swt/widget_listener_proxy'
23
+ require 'glimmer/swt/color_proxy'
24
+ require 'glimmer/swt/font_proxy'
25
+ require 'glimmer/swt/swt_proxy'
26
+ require 'glimmer/swt/display_proxy'
27
+ require 'glimmer/swt/dnd_proxy'
28
+ require 'glimmer/swt/image_proxy'
29
+ require 'glimmer/swt/properties'
30
+ require 'glimmer/swt/custom/drawable'
31
+
32
+ # TODO refactor to make file smaller and extract sub-widget-proxies out of this
33
+
34
+ module Glimmer
35
+ module SWT
36
+ # Proxy for SWT Widget objects
37
+ #
38
+ # Sets default SWT styles to widgets upon inititalizing as
39
+ # per DEFAULT_STYLES
40
+ #
41
+ # Also, auto-initializes widgets as per initializer blocks
42
+ # in DEFAULT_INITIALIZERS (e.g. setting Composite default layout)
43
+ #
44
+ # Follows the Proxy Design Pattern
45
+ class WidgetProxy
46
+ include Packages
47
+ include Properties
48
+ include Custom::Drawable
49
+
50
+ DEFAULT_STYLES = {
51
+ 'arrow' => [:arrow],
52
+ 'button' => [:push],
53
+ 'canvas' => [:double_buffered],
54
+ 'checkbox' => [:check],
55
+ 'check' => [:check],
56
+ 'drag_source' => [:drop_copy],
57
+ 'drop_target' => [:drop_copy],
58
+ 'expand_bar' => [:v_scroll],
59
+ 'list' => [:border, :v_scroll],
60
+ 'menu_item' => [:push],
61
+ 'radio' => [:radio],
62
+ 'scrolled_composite' => [:border, :h_scroll, :v_scroll],
63
+ 'spinner' => [:border],
64
+ 'styled_text' => [:border, :multi, :v_scroll, :h_scroll],
65
+ 'table' => [:virtual, :border, :full_selection],
66
+ 'text' => [:border],
67
+ 'toggle' => [:toggle],
68
+ 'tool_bar' => [:push],
69
+ 'tool_item' => [:push],
70
+ 'tree' => [:virtual, :border, :h_scroll, :v_scroll],
71
+ 'date_drop_down' => [:date, :drop_down],
72
+ 'time' => [:time],
73
+ 'calendar' => [:calendar],
74
+ }
75
+
76
+ DEFAULT_INITIALIZERS = {
77
+ composite: lambda do |composite|
78
+ composite.layout = GridLayout.new if composite.get_layout.nil?
79
+ end,
80
+ canvas: lambda do |canvas|
81
+ canvas.layout = nil if canvas.respond_to?('layout=') && !canvas.get_layout.nil?
82
+ end,
83
+ scrolled_composite: lambda do |scrolled_composite|
84
+ scrolled_composite.expand_horizontal = true
85
+ scrolled_composite.expand_vertical = true
86
+ end,
87
+ table: lambda do |table|
88
+ table.setHeaderVisible(true)
89
+ table.setLinesVisible(true)
90
+ end,
91
+ table_column: lambda do |table_column|
92
+ table_column.setWidth(80)
93
+ end,
94
+ group: lambda do |group|
95
+ group.layout = GridLayout.new if group.get_layout.nil?
96
+ end,
97
+ }
98
+
99
+ KEYWORD_ALIASES = {
100
+ 'arrow' => 'button',
101
+ 'checkbox' => 'button',
102
+ 'check' => 'button',
103
+ 'radio' => 'button',
104
+ 'toggle' => 'button',
105
+ 'date' => 'date_time',
106
+ 'date_drop_down' => 'date_time',
107
+ 'time' => 'date_time',
108
+ 'calendar' => 'date_time',
109
+ }
110
+
111
+ class << self
112
+ # Instantiates the right WidgetProxy subclass for passed in keyword
113
+ # Args are: keyword, parent, swt_widget_args (including styles)
114
+ def create(*init_args, swt_widget: nil)
115
+ return swt_widget.get_data('proxy') if swt_widget&.get_data('proxy')
116
+ keyword, parent, args = init_args
117
+ selected_widget_proxy_class = widget_proxy_class(keyword || underscored_widget_name(swt_widget))
118
+ if init_args.empty?
119
+ selected_widget_proxy_class.new(swt_widget: swt_widget)
120
+ else
121
+ selected_widget_proxy_class.new(*init_args)
122
+ end
123
+ end
124
+
125
+ def widget_proxy_class(keyword)
126
+ begin
127
+ keyword = KEYWORD_ALIASES[keyword] if KEYWORD_ALIASES[keyword]
128
+ class_name = "#{keyword.camelcase(:upper)}Proxy".to_sym
129
+ Glimmer::SWT.const_get(class_name)
130
+ rescue
131
+ Glimmer::SWT::WidgetProxy
132
+ end
133
+ end
134
+
135
+ def underscored_widget_name(swt_widget)
136
+ swt_widget.class.name.split(/::|\./).last.underscore
137
+ end
138
+ end
139
+
140
+ attr_reader :parent_proxy, :swt_widget, :drag_source_proxy, :drop_target_proxy, :drag_source_style, :drag_source_transfer, :drop_target_transfer
141
+
142
+ # Initializes a new SWT Widget
143
+ #
144
+ # It is preferred to use `::create` method instead since it instantiates the
145
+ # right subclass per widget keyword
146
+ #
147
+ # keyword, parent, swt_widget_args (including styles)
148
+ #
149
+ # Styles is a comma separate list of symbols representing SWT styles in lower case
150
+ def initialize(*init_args, swt_widget: nil)
151
+ if swt_widget.nil?
152
+ underscored_widget_name, parent, args = init_args
153
+ @parent_proxy = parent
154
+ styles, extra_options = extract_args(underscored_widget_name, args)
155
+ swt_widget_class = self.class.swt_widget_class_for(underscored_widget_name)
156
+ @swt_widget = swt_widget_class.new(@parent_proxy.swt_widget, style(underscored_widget_name, styles), *extra_options)
157
+ else
158
+ @swt_widget = swt_widget
159
+ underscored_widget_name = self.class.underscored_widget_name(@swt_widget)
160
+ parent_proxy_class = self.class.widget_proxy_class(self.class.underscored_widget_name(@swt_widget.parent))
161
+ parent = swt_widget.parent
162
+ @parent_proxy = parent&.get_data('proxy') || parent_proxy_class.new(swt_widget: parent)
163
+ end
164
+ if @swt_widget&.get_data('proxy').nil?
165
+ @swt_widget.set_data('proxy', self)
166
+ DEFAULT_INITIALIZERS[underscored_widget_name.to_s.to_sym]&.call(@swt_widget)
167
+ @parent_proxy.post_initialize_child(self)
168
+ end
169
+ @keyword = underscored_widget_name.to_s
170
+ if respond_to?(:on_widget_disposed)
171
+ on_widget_disposed {
172
+ clear_shapes
173
+ }
174
+ end
175
+ end
176
+
177
+ # Subclasses may override to perform post initialization work on an added child
178
+ def post_initialize_child(child)
179
+ # No Op by default
180
+ end
181
+
182
+ # Subclasses may override to perform post add_content work.
183
+ # Make sure its logic detects if it ran before since it could run multiple times
184
+ # when adding content multiple times post creation.
185
+ def post_add_content
186
+ # No Op by default
187
+ end
188
+
189
+ def extract_args(underscored_widget_name, args)
190
+ @arg_extractor_mapping ||= {
191
+ 'menu_item' => lambda do |args|
192
+ index = args.delete(args.last) if args.last.is_a?(Numeric)
193
+ extra_options = [index].compact
194
+ styles = args
195
+ [styles, extra_options]
196
+ end,
197
+ }
198
+ if @arg_extractor_mapping[underscored_widget_name]
199
+ @arg_extractor_mapping[underscored_widget_name].call(args)
200
+ else
201
+ extra_options = []
202
+ style_args = args.select {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
203
+ if style_args.any?
204
+ style_arg_start_index = args.index(style_args.first)
205
+ style_arg_last_index = args.index(style_args.last)
206
+ extra_options = args[style_arg_last_index+1..-1]
207
+ args = args[style_arg_start_index..style_arg_last_index]
208
+ elsif args.first.is_a?(Integer)
209
+ extra_options = args[1..-1]
210
+ args = args[0..0]
211
+ end
212
+ [args, extra_options]
213
+ end
214
+ end
215
+
216
+ def has_attribute_getter?(attribute_getter_name, *args)
217
+ attribute_getter_name = attribute_getter_name.to_s.underscore
218
+ return false unless !attribute_getter_name.end_with?('=') && !attribute_getter_name.start_with?('set_')
219
+ args.empty? && swt_widget.respond_to?(attribute_getter_name)
220
+ end
221
+
222
+ def has_attribute_setter?(attribute_setter_name, *args)
223
+ attribute_setter_name = attribute_setter_name.to_s
224
+ underscored_attribute_setter_name = attribute_setter_name.underscore
225
+ return false unless attribute_setter_name.end_with?('=') || (attribute_setter_name.start_with?('set_') && !args.empty?)
226
+ attribute_name = underscored_attribute_setter_name.sub(/^set_/, '').sub(/=$/, '')
227
+ has_attribute?(attribute_name, *args)
228
+ end
229
+
230
+ def has_attribute?(attribute_name, *args)
231
+ # TODO test that attribute getter responds too
232
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
233
+ if widget_custom_attribute
234
+ @swt_widget.respond_to?(widget_custom_attribute[:setter][:name])
235
+ else
236
+ @swt_widget.respond_to?(attribute_setter(attribute_name), args) || respond_to?(ruby_attribute_setter(attribute_name), args)
237
+ end
238
+ end
239
+
240
+ def set_attribute(attribute_name, *args)
241
+ # TODO Think about widget subclasses overriding set_attribute to add more attributes vs adding as Ruby attributes directly
242
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
243
+ apply_property_type_converters(normalized_attribute(attribute_name), args)
244
+ if widget_custom_attribute
245
+ widget_custom_attribute[:setter][:invoker].call(@swt_widget, args)
246
+ elsif @swt_widget.respond_to?(attribute_setter(attribute_name))
247
+ @swt_widget.send(attribute_setter(attribute_name), *args) unless @swt_widget.send(attribute_getter(attribute_name)) == args.first
248
+ elsif @swt_widget.respond_to?(ruby_attribute_setter(attribute_name))
249
+ @swt_widget.send(ruby_attribute_setter(attribute_name), args)
250
+ else
251
+ send(ruby_attribute_setter(attribute_name), args)
252
+ end
253
+ end
254
+
255
+ def get_attribute(attribute_name)
256
+ widget_custom_attribute = widget_custom_attribute_mapping[attribute_name.to_s]
257
+ if widget_custom_attribute
258
+ if widget_custom_attribute[:getter][:invoker]
259
+ widget_custom_attribute[:getter][:invoker].call(@swt_widget, [])
260
+ else
261
+ @swt_widget.send(widget_custom_attribute[:getter][:name])
262
+ end
263
+ elsif @swt_widget.respond_to?(attribute_getter(attribute_name))
264
+ @swt_widget.send(attribute_getter(attribute_name))
265
+ elsif @swt_widget.respond_to?(ruby_attribute_getter(attribute_name))
266
+ @swt_widget.send(ruby_attribute_getter(attribute_name))
267
+ elsif @swt_widget.respond_to?(attribute_name)
268
+ @swt_widget.send(attribute_name)
269
+ elsif respond_to?(ruby_attribute_getter(attribute_name))
270
+ send(ruby_attribute_getter(attribute_name))
271
+ else
272
+ send(attribute_name)
273
+ end
274
+ end
275
+
276
+ def pack_same_size
277
+ bounds = @swt_widget.getBounds
278
+ listener = on_control_resized {
279
+ @swt_widget.setSize(bounds.width, bounds.height)
280
+ @swt_widget.setLocation(bounds.x, bounds.y)
281
+ }
282
+ if @swt_widget.is_a?(Composite)
283
+ @swt_widget.layout(true, true)
284
+ else
285
+ @swt_widget.pack(true)
286
+ end
287
+ @swt_widget.removeControlListener(listener.swt_listener)
288
+ end
289
+
290
+ def widget_property_listener_installers
291
+ @swt_widget_property_listener_installers ||= {
292
+ Java::OrgEclipseSwtWidgets::Control => {
293
+ :focus => lambda do |observer|
294
+ on_focus_gained { |focus_event|
295
+ observer.call(true)
296
+ }
297
+ on_focus_lost { |focus_event|
298
+ observer.call(false)
299
+ }
300
+ end,
301
+ :selection => lambda do |observer|
302
+ on_widget_selected { |selection_event|
303
+ observer.call(@swt_widget.getSelection)
304
+ } if can_handle_observation_request?(:on_widget_selected)
305
+ end,
306
+ :text => lambda do |observer|
307
+ on_modify_text { |modify_event|
308
+ observer.call(@swt_widget.getText)
309
+ } if can_handle_observation_request?(:on_modify_text)
310
+ end,
311
+ },
312
+ Java::OrgEclipseSwtWidgets::Combo => {
313
+ :text => lambda do |observer|
314
+ on_modify_text { |modify_event|
315
+ observer.call(@swt_widget.getText)
316
+ }
317
+ end,
318
+ },
319
+ Java::OrgEclipseSwtWidgets::Table => {
320
+ :selection => lambda do |observer|
321
+ on_widget_selected { |selection_event|
322
+ if has_style?(:multi)
323
+ observer.call(@swt_widget.getSelection.map(&:get_data))
324
+ else
325
+ observer.call(@swt_widget.getSelection.first&.get_data)
326
+ end
327
+ }
328
+ end,
329
+ },
330
+ Java::OrgEclipseSwtWidgets::Text => {
331
+ :text => lambda do |observer|
332
+ on_modify_text { |modify_event|
333
+ observer.call(@swt_widget.getText)
334
+ }
335
+ end,
336
+ :caret_position => lambda do |observer|
337
+ on_swt_keydown { |event|
338
+ observer.call(@swt_widget.getCaretPosition)
339
+ }
340
+ on_swt_keyup { |event|
341
+ observer.call(@swt_widget.getCaretPosition)
342
+ }
343
+ on_swt_mousedown { |event|
344
+ observer.call(@swt_widget.getCaretPosition)
345
+ }
346
+ on_swt_mouseup { |event|
347
+ observer.call(@swt_widget.getCaretPosition)
348
+ }
349
+ end,
350
+ :selection => lambda do |observer|
351
+ on_swt_keydown { |event|
352
+ observer.call(@swt_widget.getSelection)
353
+ }
354
+ on_swt_keyup { |event|
355
+ observer.call(@swt_widget.getSelection)
356
+ }
357
+ on_swt_mousedown { |event|
358
+ observer.call(@swt_widget.getSelection)
359
+ }
360
+ on_swt_mouseup { |event|
361
+ observer.call(@swt_widget.getSelection)
362
+ }
363
+ end,
364
+ :selection_count => lambda do |observer|
365
+ on_swt_keydown { |event|
366
+ observer.call(@swt_widget.getSelectionCount)
367
+ }
368
+ on_swt_keyup { |event|
369
+ observer.call(@swt_widget.getSelectionCount)
370
+ }
371
+ on_swt_mousedown { |event|
372
+ observer.call(@swt_widget.getSelectionCount)
373
+ }
374
+ on_swt_mouseup { |event|
375
+ observer.call(@swt_widget.getSelectionCount)
376
+ }
377
+ end,
378
+ :top_index => lambda do |observer|
379
+ @last_top_index = @swt_widget.getTopIndex
380
+ on_paint_control { |event|
381
+ if @swt_widget.getTopIndex != @last_top_index
382
+ @last_top_index = @swt_widget.getTopIndex
383
+ observer.call(@last_top_index)
384
+ end
385
+ }
386
+ end,
387
+ },
388
+ Java::OrgEclipseSwtCustom::StyledText => {
389
+ :text => lambda do |observer|
390
+ on_modify_text { |modify_event|
391
+ observer.call(@swt_widget.getText)
392
+ }
393
+ end,
394
+ :caret_position => lambda do |observer|
395
+ on_caret_moved { |event|
396
+ observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
397
+ }
398
+ on_swt_keyup { |event|
399
+ observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
400
+ }
401
+ on_swt_mouseup { |event|
402
+ observer.call(@swt_widget.getSelection.x) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
403
+ }
404
+ end,
405
+ :caret_offset => lambda do |observer|
406
+ on_caret_moved { |event|
407
+ observer.call(@swt_widget.getCaretOffset) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
408
+ }
409
+ end,
410
+ :selection => lambda do |observer|
411
+ on_widget_selected { |event|
412
+ observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
413
+ }
414
+ on_swt_keyup { |event|
415
+ observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
416
+ }
417
+ on_swt_mouseup { |event|
418
+ observer.call(@swt_widget.getSelection) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
419
+ }
420
+ end,
421
+ :selection_count => lambda do |observer|
422
+ on_widget_selected { |event|
423
+ observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
424
+ }
425
+ on_swt_keyup { |event|
426
+ observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
427
+ }
428
+ on_swt_mouseup { |event|
429
+ observer.call(@swt_widget.getSelectionCount) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
430
+ }
431
+ end,
432
+ :selection_range => lambda do |observer|
433
+ on_widget_selected { |event|
434
+ observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
435
+ }
436
+ on_swt_keyup { |event|
437
+ observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
438
+ }
439
+ on_swt_mouseup { |event|
440
+ observer.call(@swt_widget.getSelectionRange) unless @swt_widget.getCaretOffset == 0 && @last_modify_text != text
441
+ }
442
+ end,
443
+ :top_index => lambda do |observer|
444
+ @last_top_index = @swt_widget.getTopIndex
445
+ on_paint_control { |event|
446
+ if @swt_widget.getTopIndex != @last_top_index
447
+ @last_top_index = @swt_widget.getTopIndex
448
+ observer.call(@last_top_index)
449
+ end
450
+ }
451
+ end,
452
+ :top_pixel => lambda do |observer|
453
+ @last_top_pixel = @swt_widget.getTopPixel
454
+ on_paint_control { |event|
455
+ if @swt_widget.getTopPixel != @last_top_pixel
456
+ @last_top_pixel = @swt_widget.getTopPixel
457
+ observer.call(@last_top_pixel)
458
+ end
459
+ }
460
+ end,
461
+ },
462
+ Java::OrgEclipseSwtWidgets::Button => {
463
+ :selection => lambda do |observer|
464
+ on_widget_selected { |selection_event|
465
+ observer.call(@swt_widget.getSelection)
466
+ }
467
+ end
468
+ },
469
+ Java::OrgEclipseSwtWidgets::MenuItem => {
470
+ :selection => lambda do |observer|
471
+ on_widget_selected { |selection_event|
472
+ observer.call(@swt_widget.getSelection)
473
+ }
474
+ end
475
+ },
476
+ Java::OrgEclipseSwtWidgets::Spinner => {
477
+ :selection => lambda do |observer|
478
+ on_widget_selected { |selection_event|
479
+ observer.call(@swt_widget.getSelection)
480
+ }
481
+ end
482
+ },
483
+ Java::OrgEclipseSwtWidgets::DateTime =>
484
+ [:year, :month, :day, :hours, :minutes, :seconds, :date_time, :date, :time].reduce({}) do |hash, attribute|
485
+ hash.merge(
486
+ attribute => lambda do |observer|
487
+ on_widget_selected { |selection_event|
488
+ observer.call(get_attribute(attribute))
489
+ }
490
+ end
491
+ )
492
+ end,
493
+ }
494
+ end
495
+
496
+ def self.widget_exists?(underscored_widget_name)
497
+ !!swt_widget_class_for(underscored_widget_name)
498
+ end
499
+
500
+ # Manual entries of SWT widget classes that conflict with Ruby classes
501
+ def self.swt_widget_class_manual_entries
502
+ {
503
+ 'date_time' => Java::OrgEclipseSwtWidgets::DateTime
504
+ }
505
+ end
506
+
507
+ # This supports widgets in and out of basic SWT
508
+ def self.swt_widget_class_for(underscored_widget_name)
509
+ # TODO clear memoization for a keyword if a custom widget was defined with that keyword
510
+ unless flyweight_swt_widget_classes.keys.include?(underscored_widget_name)
511
+ begin
512
+ underscored_widget_name = KEYWORD_ALIASES[underscored_widget_name] if KEYWORD_ALIASES[underscored_widget_name]
513
+ swt_widget_name = underscored_widget_name.camelcase(:upper)
514
+ swt_widget_class = eval(swt_widget_name)
515
+ # TODO fix issue with not detecting DateTime because it's conflicting with the Ruby DateTime
516
+ unless swt_widget_class.ancestors.include?(org.eclipse.swt.widgets.Widget)
517
+ swt_widget_class = swt_widget_class_manual_entries[underscored_widget_name]
518
+ if swt_widget_class.nil?
519
+ Glimmer::Config.logger.debug {"Class #{swt_widget_class} matching #{underscored_widget_name} is not a subclass of org.eclipse.swt.widgets.Widget"}
520
+ return nil
521
+ end
522
+ end
523
+ flyweight_swt_widget_classes[underscored_widget_name] = swt_widget_class
524
+ rescue SyntaxError, NameError => e
525
+ Glimmer::Config.logger.debug {e.full_message}
526
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
527
+ nil
528
+ rescue => e
529
+ Glimmer::Config.logger.debug {e.full_message}
530
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
531
+ nil
532
+ end
533
+ end
534
+ flyweight_swt_widget_classes[underscored_widget_name]
535
+ end
536
+
537
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
538
+ def self.flyweight_swt_widget_classes
539
+ @flyweight_swt_widget_classes ||= {}
540
+ end
541
+
542
+ def async_exec(&block)
543
+ DisplayProxy.instance.async_exec(&block)
544
+ end
545
+
546
+ def sync_exec(&block)
547
+ DisplayProxy.instance.sync_exec(&block)
548
+ end
549
+
550
+ def timer_exec(delay_in_millis, &block)
551
+ DisplayProxy.instance.timer_exec(delay_in_millis, &block)
552
+ end
553
+
554
+ def has_style?(style)
555
+ comparison = interpret_style(style)
556
+ (@swt_widget.style & comparison) == comparison
557
+ end
558
+
559
+ def dispose
560
+ @swt_widget.dispose
561
+ end
562
+
563
+ def disposed?
564
+ @swt_widget.isDisposed
565
+ end
566
+
567
+ # TODO Consider renaming these methods as they are mainly used for data-binding
568
+
569
+ def can_add_observer?(property_name)
570
+ @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact.map(&:keys).flatten.map(&:to_s).include?(property_name.to_s)
571
+ end
572
+
573
+ # Used for data-binding only. Consider renaming or improving to avoid the confusion it causes
574
+ def add_observer(observer, property_name)
575
+ if !observer.respond_to?(:binding_options) || !observer.binding_options[:read_only]
576
+ property_listener_installers = @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
577
+ widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
578
+ widget_listener_installers.to_a.first&.call(observer)
579
+ end
580
+ end
581
+
582
+ def remove_observer(observer, property_name)
583
+ # TODO consider implementing if remove_observer is needed (consumers can remove listener via SWT API)
584
+ end
585
+
586
+ def ensure_drag_source_proxy(style=[])
587
+ @drag_source_proxy ||= self.class.new('drag_source', self, style).tap do |proxy|
588
+ proxy.set_attribute(:transfer, :text)
589
+ end
590
+ end
591
+
592
+ def ensure_drop_target_proxy(style=[])
593
+ @drop_target_proxy ||= self.class.new('drop_target', self, style).tap do |proxy|
594
+ proxy.set_attribute(:transfer, :text)
595
+ proxy.on_drag_enter { |event|
596
+ event.detail = DNDProxy[:drop_copy]
597
+ }
598
+ end
599
+ end
600
+
601
+ # TODO eliminate duplication in the following methods perhaps by relying on exceptions
602
+
603
+ def can_handle_observation_request?(observation_request)
604
+ observation_request = observation_request.to_s
605
+ if observation_request.start_with?('on_swt_')
606
+ constant_name = observation_request.sub(/^on_swt_/, '')
607
+ SWTProxy.has_constant?(constant_name)
608
+ elsif observation_request.start_with?('on_')
609
+ event = observation_request.sub(/^on_/, '')
610
+ can_add_listener?(event) || can_handle_drag_observation_request?(observation_request) || can_handle_drop_observation_request?(observation_request)
611
+ end
612
+ end
613
+
614
+ def can_handle_drag_observation_request?(observation_request)
615
+ return false unless swt_widget.is_a?(Control)
616
+ potential_drag_source = @drag_source_proxy.nil?
617
+ ensure_drag_source_proxy
618
+ @drag_source_proxy.can_handle_observation_request?(observation_request).tap do |result|
619
+ if potential_drag_source && !result
620
+ @drag_source_proxy.swt_widget.dispose
621
+ @drag_source_proxy = nil
622
+ end
623
+ end
624
+ rescue => e
625
+ Glimmer::Config.logger.debug {e.full_message}
626
+ false
627
+ end
628
+
629
+ def can_handle_drop_observation_request?(observation_request)
630
+ return false unless swt_widget.is_a?(Control)
631
+ potential_drop_target = @drop_target_proxy.nil?
632
+ ensure_drop_target_proxy
633
+ @drop_target_proxy.can_handle_observation_request?(observation_request).tap do |result|
634
+ if potential_drop_target && !result
635
+ @drop_target_proxy.swt_widget.dispose
636
+ @drop_target_proxy = nil
637
+ end
638
+ end
639
+ end
640
+
641
+ def handle_observation_request(observation_request, &block)
642
+ observation_request = observation_request.to_s
643
+ if observation_request.start_with?('on_swt_')
644
+ constant_name = observation_request.sub(/^on_swt_/, '')
645
+ add_swt_event_listener(constant_name, &block)
646
+ elsif observation_request.start_with?('on_')
647
+ event = observation_request.sub(/^on_/, '')
648
+ if can_add_listener?(event)
649
+ event = observation_request.sub(/^on_/, '')
650
+ add_listener(event, &block)
651
+ elsif can_handle_drag_observation_request?(observation_request)
652
+ @drag_source_proxy&.handle_observation_request(observation_request, &block)
653
+ elsif can_handle_drop_observation_request?(observation_request)
654
+ @drop_target_proxy&.handle_observation_request(observation_request, &block)
655
+ end
656
+ end
657
+ end
658
+
659
+ def content(&block)
660
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, &block)
661
+ end
662
+
663
+ def method_missing(method, *args, &block)
664
+ if can_handle_observation_request?(method)
665
+ handle_observation_request(method, &block)
666
+ elsif has_attribute_setter?(method, *args)
667
+ set_attribute(method, *args)
668
+ elsif has_attribute_getter?(method, *args)
669
+ get_attribute(method, *args)
670
+ else
671
+ swt_widget.send(method, *args, &block)
672
+ end
673
+ rescue => e
674
+ Glimmer::Config.logger.debug { "Neither WidgetProxy nor #{swt_widget.class.name} can handle the method ##{method}" }
675
+ Glimmer::Config.logger.debug { e.full_message }
676
+ super
677
+ # TODO consider get_attribute too
678
+ end
679
+
680
+ def respond_to?(method, *args, &block)
681
+ super ||
682
+ can_handle_observation_request?(method) ||
683
+ swt_widget.respond_to?(method, *args, &block)
684
+ end
685
+
686
+ private
687
+
688
+ def style(underscored_widget_name, styles)
689
+ styles = [styles].flatten.compact
690
+ styles = default_style(underscored_widget_name) if styles.empty?
691
+ interpret_style(*styles)
692
+ end
693
+
694
+ def interpret_style(*styles)
695
+ SWTProxy[*styles] rescue DNDProxy[*styles]
696
+ end
697
+
698
+ def default_style(underscored_widget_name)
699
+ DEFAULT_STYLES[underscored_widget_name] || [:none]
700
+ end
701
+
702
+ # TODO refactor following methods to eliminate duplication
703
+ # perhaps consider relying on raising an exception to avoid checking first
704
+ # unless that gives obscure SWT errors
705
+ # Otherwise, consider caching results from can_add_lsitener and using them in
706
+ # add_listener knowing it will be called for sure afterwards
707
+
708
+ def can_add_listener?(underscored_listener_name)
709
+ !self.class.find_listener(@swt_widget.getClass, underscored_listener_name).empty?
710
+ end
711
+
712
+ def add_listener(underscored_listener_name, &block)
713
+ widget_add_listener_method, listener_class, listener_method = self.class.find_listener(@swt_widget.getClass, underscored_listener_name)
714
+ widget_listener_proxy = nil
715
+ safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
716
+ listener = listener_class.new(listener_method => safe_block)
717
+ @swt_widget.send(widget_add_listener_method, listener)
718
+ 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)
719
+ end
720
+
721
+ # Looks through SWT class add***Listener methods till it finds one for which
722
+ # the argument is a listener class that has an event method matching
723
+ # underscored_listener_name
724
+ def self.find_listener(swt_widget_class, underscored_listener_name)
725
+ @listeners ||= {}
726
+ listener_key = [swt_widget_class.name, underscored_listener_name]
727
+ unless @listeners.has_key?(listener_key)
728
+ listener_method_name = underscored_listener_name.camelcase(:lower)
729
+ swt_widget_class.getMethods.each do |widget_add_listener_method|
730
+ if widget_add_listener_method.getName.match(/add.*Listener/)
731
+ widget_add_listener_method.getParameterTypes.each do |listener_type|
732
+ listener_type.getMethods.each do |listener_method|
733
+ if (listener_method.getName == listener_method_name)
734
+ @listeners[listener_key] = [widget_add_listener_method.getName, listener_class(listener_type), listener_method.getName]
735
+ return @listeners[listener_key]
736
+ end
737
+ end
738
+ end
739
+ end
740
+ end
741
+ @listeners[listener_key] = []
742
+ end
743
+ @listeners[listener_key]
744
+ end
745
+
746
+ # Returns a Ruby class that implements listener type Java interface with ability to easily
747
+ # install a block that gets called upon calling a listener event method
748
+ def self.listener_class(listener_type)
749
+ @listener_classes ||= {}
750
+ listener_class_key = listener_type.name
751
+ unless @listener_classes.has_key?(listener_class_key)
752
+ @listener_classes[listener_class_key] = Class.new(Object).tap do |listener_class|
753
+ listener_class.send :include, (eval listener_type.name.sub("interface", ""))
754
+ listener_class.define_method('initialize') do |event_method_block_mapping|
755
+ @event_method_block_mapping = event_method_block_mapping
756
+ end
757
+ listener_type.getMethods.each do |event_method|
758
+ listener_class.define_method(event_method.getName) do |*args|
759
+ @event_method_block_mapping[event_method.getName]&.call(*args)
760
+ end
761
+ end
762
+ end
763
+ end
764
+ @listener_classes[listener_class_key]
765
+ end
766
+
767
+ def add_swt_event_listener(swt_constant, &block)
768
+ event_type = SWTProxy[swt_constant]
769
+ widget_listener_proxy = nil
770
+ safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
771
+ @swt_widget.addListener(event_type, &safe_block)
772
+ 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)
773
+ end
774
+
775
+ def widget_custom_attribute_mapping
776
+ # TODO scope per widget class type just like other mappings
777
+ @swt_widget_custom_attribute_mapping ||= {
778
+ 'focus' => {
779
+ getter: {name: 'isFocusControl'},
780
+ setter: {name: 'setFocus', invoker: lambda { |widget, args| @swt_widget.setFocus if args.first }},
781
+ },
782
+ 'caret_position' => {
783
+ getter: {name: 'getCaretPosition', invoker: lambda { |widget, args| @swt_widget.respond_to?(:getCaretPosition) ? @swt_widget.getCaretPosition : @swt_widget.getSelection.x}},
784
+ setter: {name: 'setSelection', invoker: lambda { |widget, args| @swt_widget.setSelection(args.first, args.first + @swt_widget.getSelectionCount) if args.first }},
785
+ },
786
+ 'selection_count' => {
787
+ getter: {name: 'getSelectionCount'},
788
+ setter: {name: 'setSelection', invoker: lambda { |widget, args|
789
+ if args.first
790
+ caret_position = @swt_widget.respond_to?(:getCaretPosition) ? @swt_widget.getCaretPosition : @swt_widget.getSelection.x
791
+ # TODO handle negative length
792
+ @swt_widget.setSelection(caret_position, caret_position + args.first)
793
+ end
794
+ }},
795
+ },
796
+ }
797
+ end
798
+
799
+ def drag_source_style=(style)
800
+ ensure_drag_source_proxy(style)
801
+ end
802
+
803
+ def drop_target_style=(style)
804
+ ensure_drop_target_proxy(style)
805
+ end
806
+
807
+ def drag_source_transfer=(args)
808
+ args = args.first if !args.empty? && args.first.is_a?(ArrayJavaProxy)
809
+ ensure_drag_source_proxy
810
+ @drag_source_proxy.set_attribute(:transfer, args)
811
+ end
812
+
813
+ def drop_target_transfer=(args)
814
+ args = args.first if !args.empty? && args.first.is_a?(ArrayJavaProxy)
815
+ ensure_drop_target_proxy
816
+ @drop_target_proxy.set_attribute(:transfer, args)
817
+ end
818
+
819
+ def drag_source_effect=(args)
820
+ args = args.first if args.is_a?(Array)
821
+ ensure_drag_source_proxy
822
+ @drag_source_proxy.set_attribute(:drag_source_effect, args)
823
+ end
824
+
825
+ def drop_target_effect=(args)
826
+ args = args.first if args.is_a?(Array)
827
+ ensure_drop_target_proxy
828
+ @drop_target_proxy.set_attribute(:drop_target_effect, args)
829
+ end
830
+
831
+ def apply_property_type_converters(attribute_name, args)
832
+ value = args
833
+ converter = property_type_converters[attribute_name.to_sym]
834
+ args[0..-1] = [converter.call(*value)] if converter
835
+ if args.count == 1 && args.first.is_a?(ColorProxy)
836
+ g_color = args.first
837
+ args[0] = g_color.swt_color
838
+ end
839
+ end
840
+
841
+ def property_type_converters
842
+ color_converter = lambda do |value|
843
+ if value.is_a?(Symbol) || value.is_a?(String)
844
+ ColorProxy.new(value).swt_color
845
+ else
846
+ value
847
+ end
848
+ end
849
+ # TODO consider detecting type on widget method and automatically invoking right converter (e.g. :to_s for String, :to_i for Integer)
850
+ @property_type_converters ||= {
851
+ accelerator: lambda { |*value|
852
+ SWTProxy[*value]
853
+ },
854
+ alignment: lambda { |*value|
855
+ SWTProxy[*value]
856
+ },
857
+ background: color_converter,
858
+ background_image: lambda do |*value|
859
+ image_proxy = ImageProxy.create(*value)
860
+
861
+ if image_proxy&.file_path&.end_with?('.gif')
862
+ image = image_proxy.swt_image
863
+ width = image.get_bounds.width.to_i
864
+ height = image.get_bounds.height.to_i
865
+ image_number = 0
866
+ loader = ImageLoader.new
867
+ loader.load(image_proxy.input_stream)
868
+ image.dispose
869
+ image = org.eclipse.swt.graphics.Image.new(DisplayProxy.instance.swt_display,loader.data[0].scaledTo(width, height))
870
+ gc = org.eclipse.swt.graphics.GC.new(image)
871
+ on_paint_control { |event|
872
+ image_number = (image_number == loader.data.length - 1) ? 0 : image_number + 1
873
+ next_frame_data = loader.data[image_number]
874
+ image = org.eclipse.swt.graphics.Image.new(DisplayProxy.instance.swt_display, next_frame_data.scaledTo(width, height))
875
+ event.gc.drawImage(image, 0, 0, width, height, 0, 0, width, height)
876
+ image.dispose
877
+ }
878
+ Thread.new {
879
+ last_image_number = -1
880
+ while last_image_number != image_number
881
+ last_image_number = image_number
882
+ sync_exec {
883
+ redraw
884
+ }
885
+ delayTime = loader.data[image_number].delayTime.to_f / 100.0
886
+ sleep(delayTime)
887
+ end
888
+ };
889
+ image_proxy = nil
890
+ else
891
+ on_swt_Resize do |resize_event|
892
+ image_proxy.scale_to(@swt_widget.getSize.x, @swt_widget.getSize.y)
893
+ @swt_widget.setBackgroundImage(image_proxy.swt_image)
894
+ end
895
+ end
896
+
897
+ image_proxy&.swt_image
898
+ end,
899
+ cursor: lambda do |value|
900
+ cursor_proxy = nil
901
+ if value.is_a?(CursorProxy)
902
+ cursor_proxy = value
903
+ elsif value.is_a?(Symbol) || value.is_a?(String) || value.is_a?(Integer)
904
+ cursor_proxy = CursorProxy.new(value)
905
+ end
906
+ cursor_proxy ? cursor_proxy.swt_cursor : value
907
+ end,
908
+ :enabled => lambda do |value|
909
+ !!value
910
+ end,
911
+ foreground: color_converter,
912
+ link_foreground: color_converter,
913
+ font: lambda do |value|
914
+ if value.is_a?(Hash)
915
+ font_properties = value
916
+ FontProxy.new(self, font_properties).swt_font
917
+ else
918
+ value
919
+ end
920
+ end,
921
+ image: lambda do |*value|
922
+ ImageProxy.create(*value).swt_image
923
+ end,
924
+ images: lambda do |array|
925
+ array.to_a.map do |value|
926
+ ImageProxy.create(value).swt_image
927
+ end.to_java(Image)
928
+ end,
929
+ items: lambda do |value|
930
+ value.to_java :string
931
+ end,
932
+ text: lambda do |value|
933
+ value.to_s
934
+ end,
935
+ transfer: lambda do |value|
936
+ value = value.first if value.is_a?(Array) && value.size == 1 && value.first.is_a?(Array)
937
+ transfer_object_extrapolator = lambda do |transfer_name|
938
+ transfer_type = "#{transfer_name.to_s.camelcase(:upper)}Transfer".to_sym
939
+ transfer_type_alternative = "#{transfer_name.to_s.upcase}Transfer".to_sym
940
+ transfer_class = org.eclipse.swt.dnd.const_get(transfer_type) rescue org.eclipse.swt.dnd.const_get(transfer_type_alternative)
941
+ transfer_class.getInstance
942
+ end
943
+ result = value
944
+ if value.is_a?(Symbol) || value.is_a?(String)
945
+ result = [transfer_object_extrapolator.call(value)]
946
+ elsif value.is_a?(Array)
947
+ result = value.map do |transfer_name|
948
+ transfer_object_extrapolator.call(transfer_name)
949
+ end
950
+ end
951
+ result = result.to_java(Transfer) unless result.is_a?(ArrayJavaProxy)
952
+ result
953
+ end,
954
+ visible: lambda do |value|
955
+ !!value
956
+ end,
957
+ weights: lambda do |value|
958
+ value.to_java(:int)
959
+ end,
960
+ }
961
+ end
962
+ end
963
+ end
964
+ end