glimmer-dsl-web 0.0.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +636 -0
- data/VERSION +1 -0
- data/app/assets/stylesheets/glimmer/glimmer.css +15 -0
- data/glimmer-dsl-web.gemspec +67 -0
- data/lib/glimmer/config/opal_logger.rb +16 -0
- data/lib/glimmer/data_binding/element_binding.rb +36 -0
- data/lib/glimmer/data_binding/observable_element.rb +14 -0
- data/lib/glimmer/dsl/web/dsl.rb +24 -0
- data/lib/glimmer/dsl/web/element_expression.rb +37 -0
- data/lib/glimmer/util/proc_tracker.rb +50 -0
- data/lib/glimmer/web/element_proxy.rb +1058 -0
- data/lib/glimmer/web/property_owner.rb +24 -0
- data/lib/glimmer/web.rb +25 -0
- data/lib/glimmer-dsl-web/ext/class.rb +10 -0
- data/lib/glimmer-dsl-web/ext/date.rb +60 -0
- data/lib/glimmer-dsl-web/ext/exception.rb +6 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_world.rb +29 -0
- data/lib/glimmer-dsl-web/vendor/jquery.js +10881 -0
- data/lib/glimmer-dsl-web.rb +99 -0
- metadata +295 -0
@@ -0,0 +1,1058 @@
|
|
1
|
+
# Copyright (c) 2020-2022 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/web/event_listener_proxy'
|
23
|
+
require 'glimmer/web/property_owner'
|
24
|
+
|
25
|
+
# TODO implement menu (which delays building it till render using add_content_on_render)
|
26
|
+
|
27
|
+
module Glimmer
|
28
|
+
module Web
|
29
|
+
class ElementProxy
|
30
|
+
class << self
|
31
|
+
# Factory Method that translates a Glimmer DSL keyword into a ElementProxy object
|
32
|
+
def for(keyword, parent, args, block)
|
33
|
+
element_type(keyword).new(keyword, parent, args, block)
|
34
|
+
end
|
35
|
+
|
36
|
+
# returns Ruby proxy class (type) that would handle this keyword
|
37
|
+
def element_type(keyword)
|
38
|
+
class_name_main = "#{keyword.camelcase(:upper)}Proxy"
|
39
|
+
Glimmer::Web::ElementProxy.const_get(class_name_main.to_sym)
|
40
|
+
rescue NameError => e
|
41
|
+
Glimmer::Web::ElementProxy
|
42
|
+
end
|
43
|
+
|
44
|
+
def next_id_number_for(name)
|
45
|
+
@max_id_numbers[name] = max_id_number_for(name) + 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def max_id_number_for(name)
|
49
|
+
@max_id_numbers[name] = max_id_numbers[name] || 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def max_id_numbers
|
53
|
+
@max_id_numbers ||= reset_max_id_numbers!
|
54
|
+
end
|
55
|
+
|
56
|
+
def reset_max_id_numbers!
|
57
|
+
@max_id_numbers = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def underscored_widget_name(widget_proxy)
|
61
|
+
widget_proxy.class.name.split(/::|\./).last.sub(/Proxy$/, '').underscore
|
62
|
+
end
|
63
|
+
|
64
|
+
def widget_handling_listener
|
65
|
+
@@widget_handling_listener
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
include Glimmer
|
70
|
+
include PropertyOwner
|
71
|
+
|
72
|
+
Event = Struct.new(:widget, keyword_init: true)
|
73
|
+
|
74
|
+
attr_reader :keyword, :parent, :args, :options, :path, :children, :enabled, :foreground, :background, :focus, :removed?, :rendered
|
75
|
+
alias rendered? rendered
|
76
|
+
|
77
|
+
def initialize(keyword, parent, args, block)
|
78
|
+
@keyword = keyword
|
79
|
+
@parent = parent
|
80
|
+
@options = args.last.is_a?(Hash) ? args.last.symbolize_keys : {}
|
81
|
+
@args = args
|
82
|
+
@block = block
|
83
|
+
@children = []
|
84
|
+
@parent&.post_initialize_child(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Executes for the parent of a child that just got added
|
88
|
+
def post_initialize_child(child)
|
89
|
+
@children << child
|
90
|
+
child.render
|
91
|
+
end
|
92
|
+
|
93
|
+
# Executes for the parent of a child that just got removed
|
94
|
+
def post_remove_child(child)
|
95
|
+
@children&.delete(child)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Executes at the closing of a parent widget curly braces after all children/properties have been added/set
|
99
|
+
def post_add_content
|
100
|
+
# No Op
|
101
|
+
end
|
102
|
+
|
103
|
+
def css_classes
|
104
|
+
dom_element.attr('class').to_s.split
|
105
|
+
end
|
106
|
+
|
107
|
+
def remove
|
108
|
+
remove_all_listeners
|
109
|
+
Document.find(path).remove
|
110
|
+
parent&.post_remove_child(self)
|
111
|
+
# children.each(:remove) # TODO enable this safely
|
112
|
+
@removed = true
|
113
|
+
listeners_for('widget_removed').each {|listener| listener.call(Event.new(widget: self))}
|
114
|
+
end
|
115
|
+
|
116
|
+
def remove_all_listeners
|
117
|
+
effective_observation_request_to_event_mapping.keys.each do |keyword|
|
118
|
+
effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
|
119
|
+
observation_requests[keyword].to_a.each do |event_listener|
|
120
|
+
event = mapping[:event]
|
121
|
+
event_handler = mapping[:event_handler]
|
122
|
+
event_element_css_selector = mapping[:event_element_css_selector]
|
123
|
+
the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
|
124
|
+
the_listener_dom_element.off(event, event_listener)
|
125
|
+
# TODO improve to precisely remove the listeners that were added, no more no less. (or use the event_listener_proxies method instead or in collaboration)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def path
|
132
|
+
"#{parent_path} #{element}##{id}.#{name}"
|
133
|
+
end
|
134
|
+
alias widget_path path # pure path without subchildren modifications
|
135
|
+
|
136
|
+
# Root element representing widget. Must be overridden by subclasses if different from div
|
137
|
+
def element
|
138
|
+
keyword
|
139
|
+
end
|
140
|
+
|
141
|
+
def shell
|
142
|
+
current_widget = self
|
143
|
+
current_widget = current_widget.parent until current_widget.parent.nil?
|
144
|
+
current_widget
|
145
|
+
end
|
146
|
+
|
147
|
+
def parents
|
148
|
+
parents_array = []
|
149
|
+
current_widget = self
|
150
|
+
until current_widget.parent.nil?
|
151
|
+
current_widget = current_widget.parent
|
152
|
+
parents_array << current_widget
|
153
|
+
end
|
154
|
+
parents_array
|
155
|
+
end
|
156
|
+
|
157
|
+
def dialog_ancestor
|
158
|
+
parents.detect {|p| p.is_a?(DialogProxy)}
|
159
|
+
end
|
160
|
+
|
161
|
+
def print
|
162
|
+
`window.print()`
|
163
|
+
true
|
164
|
+
end
|
165
|
+
|
166
|
+
def enabled=(value)
|
167
|
+
@enabled = value
|
168
|
+
dom_element.prop('disabled', !@enabled)
|
169
|
+
end
|
170
|
+
|
171
|
+
def foreground=(value)
|
172
|
+
value = ColorProxy.new(value) if value.is_a?(String)
|
173
|
+
@foreground = value
|
174
|
+
dom_element.css('color', foreground.to_css) unless foreground.nil?
|
175
|
+
end
|
176
|
+
|
177
|
+
def background=(value)
|
178
|
+
value = ColorProxy.new(value) if value.is_a?(String)
|
179
|
+
@background = value
|
180
|
+
dom_element.css('background-color', background.to_css) unless background.nil?
|
181
|
+
end
|
182
|
+
|
183
|
+
def focus=(value)
|
184
|
+
@focus = value
|
185
|
+
dom_element.focus # TODO consider if a delay or async_exec is needed here
|
186
|
+
end
|
187
|
+
|
188
|
+
def set_focus
|
189
|
+
self.focus = true
|
190
|
+
end
|
191
|
+
alias setFocus set_focus
|
192
|
+
|
193
|
+
def parent_path
|
194
|
+
@parent&.path
|
195
|
+
end
|
196
|
+
|
197
|
+
def parent_dom_element
|
198
|
+
if parent_path
|
199
|
+
Document.find(parent_path)
|
200
|
+
elsif options[:root]
|
201
|
+
Document.find(options[:root])
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def render(custom_parent_dom_element: nil, brand_new: false)
|
206
|
+
the_parent_dom_element = custom_parent_dom_element || parent_dom_element
|
207
|
+
old_element = dom_element
|
208
|
+
brand_new = @dom.nil? || old_element.empty? || brand_new
|
209
|
+
build_dom(layout: !custom_parent_dom_element) # TODO handle custom parent layout by passing parent instead of parent dom element
|
210
|
+
if brand_new
|
211
|
+
# TODO make a method attach to allow subclasses to override if needed
|
212
|
+
attach(the_parent_dom_element)
|
213
|
+
else
|
214
|
+
reattach(old_element)
|
215
|
+
end
|
216
|
+
observation_requests&.each do |keyword, event_listener_set|
|
217
|
+
event_listener_set.each do |event_listener|
|
218
|
+
handle_observation_request(keyword, event_listener)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
children.each do |child|
|
222
|
+
child.render
|
223
|
+
end
|
224
|
+
@rendered = true
|
225
|
+
unless skip_content_on_render_blocks?
|
226
|
+
content_on_render_blocks.each do |content_block|
|
227
|
+
content(&content_block)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
alias redraw render
|
232
|
+
|
233
|
+
def attach(the_parent_dom_element)
|
234
|
+
the_parent_dom_element.append(@dom)
|
235
|
+
end
|
236
|
+
|
237
|
+
def reattach(old_element)
|
238
|
+
old_element.replace_with(@dom)
|
239
|
+
end
|
240
|
+
|
241
|
+
def add_text_content(text)
|
242
|
+
dom_element.append(text)
|
243
|
+
end
|
244
|
+
|
245
|
+
def content_on_render_blocks
|
246
|
+
@content_on_render_blocks ||= []
|
247
|
+
end
|
248
|
+
|
249
|
+
def skip_content_on_render_blocks?
|
250
|
+
false
|
251
|
+
end
|
252
|
+
|
253
|
+
def add_content_on_render(&content_block)
|
254
|
+
if rendered?
|
255
|
+
content_block.call
|
256
|
+
else
|
257
|
+
content_on_render_blocks << content_block
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def build_dom(layout: true)
|
262
|
+
# TODO consider passing parent element instead and having table item include a table cell widget only for opal
|
263
|
+
@dom = nil
|
264
|
+
@dom = dom # TODO unify how to build dom for most widgets based on element, id, and name (class)
|
265
|
+
@dom = @parent.get_layout.dom(@dom) if @parent.respond_to?(:layout) && @parent.get_layout
|
266
|
+
@dom
|
267
|
+
end
|
268
|
+
|
269
|
+
def dom
|
270
|
+
body_id = id
|
271
|
+
body_class = ([name] + css_classes.to_a).join(' ')
|
272
|
+
html_options = options.dup
|
273
|
+
html_options[:id] ||= body_id
|
274
|
+
html_options[:class] ||= ''
|
275
|
+
html_options[:class] = "#{html_options[:class]} #{body_class}".strip
|
276
|
+
@dom ||= html {
|
277
|
+
send(keyword, html_options) {
|
278
|
+
# TODO consider supporting the idea of dynamic CSS building on close of shell that adds only as much CSS as needed for widgets that were mentioned
|
279
|
+
# style(class: 'common-style') {
|
280
|
+
# style_dom_css
|
281
|
+
# }
|
282
|
+
# [LayoutProxy, WidgetProxy].map(&:descendants).reduce(:+).each do |style_class|
|
283
|
+
# if style_class.constants.include?('STYLE')
|
284
|
+
# style(class: "#{style_class.name.split(':').last.underscore.gsub('_', '-').sub(/-proxy$/, '')}-style") {
|
285
|
+
# style_class::STYLE
|
286
|
+
# }
|
287
|
+
# end
|
288
|
+
# end
|
289
|
+
}
|
290
|
+
}.to_s
|
291
|
+
end
|
292
|
+
|
293
|
+
def content(&block)
|
294
|
+
Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ElementExpression.new, keyword, &block)
|
295
|
+
end
|
296
|
+
|
297
|
+
# Subclasses must override with their own mappings
|
298
|
+
def observation_request_to_event_mapping
|
299
|
+
{}
|
300
|
+
end
|
301
|
+
|
302
|
+
def effective_observation_request_to_event_mapping
|
303
|
+
default_observation_request_to_event_mapping.merge(observation_request_to_event_mapping)
|
304
|
+
end
|
305
|
+
|
306
|
+
def default_observation_request_to_event_mapping
|
307
|
+
myself = self
|
308
|
+
mouse_event_handler = -> (event_listener) {
|
309
|
+
-> (event) {
|
310
|
+
# TODO generalize this solution to all widgets that support key presses
|
311
|
+
event.define_singleton_method(:widget) {myself}
|
312
|
+
event.define_singleton_method(:button, &event.method(:which))
|
313
|
+
event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
|
314
|
+
event.define_singleton_method(:x, &event.method(:page_x))
|
315
|
+
event.define_singleton_method(:y, &event.method(:page_y))
|
316
|
+
doit = true
|
317
|
+
event.define_singleton_method(:doit=) do |value|
|
318
|
+
doit = value
|
319
|
+
end
|
320
|
+
event.define_singleton_method(:doit) { doit }
|
321
|
+
|
322
|
+
if event.which == 1
|
323
|
+
# event.prevent # TODO consider if this is needed
|
324
|
+
event_listener.call(event)
|
325
|
+
end
|
326
|
+
|
327
|
+
# TODO Imlement doit properly for all different kinds of events
|
328
|
+
# unless doit
|
329
|
+
# event.prevent
|
330
|
+
# event.stop
|
331
|
+
# event.stop_immediate
|
332
|
+
# end
|
333
|
+
}
|
334
|
+
}
|
335
|
+
mouse_move_event_handler = -> (event_listener) {
|
336
|
+
-> (event) {
|
337
|
+
# TODO generalize this solution to all widgets that support key presses
|
338
|
+
event.define_singleton_method(:widget) {myself}
|
339
|
+
event.define_singleton_method(:button, &event.method(:which))
|
340
|
+
event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
|
341
|
+
event.define_singleton_method(:x, &event.method(:page_x))
|
342
|
+
event.define_singleton_method(:y, &event.method(:page_y))
|
343
|
+
doit = true
|
344
|
+
event.define_singleton_method(:doit=) do |value|
|
345
|
+
doit = value
|
346
|
+
end
|
347
|
+
event.define_singleton_method(:doit) { doit }
|
348
|
+
|
349
|
+
event_listener.call(event)
|
350
|
+
|
351
|
+
# TODO Imlement doit properly for all different kinds of events
|
352
|
+
# unless doit
|
353
|
+
# event.prevent
|
354
|
+
# event.stop
|
355
|
+
# event.stop_immediate
|
356
|
+
# end
|
357
|
+
}
|
358
|
+
}
|
359
|
+
context_menu_handler = -> (event_listener) {
|
360
|
+
-> (event) {
|
361
|
+
# TODO generalize this solution to all widgets that support key presses
|
362
|
+
event.define_singleton_method(:widget) {myself}
|
363
|
+
event.define_singleton_method(:button, &event.method(:which))
|
364
|
+
event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
|
365
|
+
event.define_singleton_method(:x, &event.method(:page_x))
|
366
|
+
event.define_singleton_method(:y, &event.method(:page_y))
|
367
|
+
doit = true
|
368
|
+
event.define_singleton_method(:doit=) do |value|
|
369
|
+
doit = value
|
370
|
+
end
|
371
|
+
event.define_singleton_method(:doit) { doit }
|
372
|
+
|
373
|
+
if event.which == 3
|
374
|
+
event.prevent
|
375
|
+
event_listener.call(event)
|
376
|
+
end
|
377
|
+
# TODO Imlement doit properly for all different kinds of events
|
378
|
+
# unless doit
|
379
|
+
# event.prevent
|
380
|
+
# event.stop
|
381
|
+
# event.stop_immediate
|
382
|
+
# end
|
383
|
+
}
|
384
|
+
}
|
385
|
+
{
|
386
|
+
'on_focus_gained' => {
|
387
|
+
event: 'focus',
|
388
|
+
},
|
389
|
+
'on_focus_lost' => {
|
390
|
+
event: 'blur',
|
391
|
+
},
|
392
|
+
'on_mouse_move' => [
|
393
|
+
{
|
394
|
+
event: 'mousemove',
|
395
|
+
event_handler: mouse_move_event_handler,
|
396
|
+
},
|
397
|
+
],
|
398
|
+
'on_mouse_up' => [
|
399
|
+
{
|
400
|
+
event: 'mouseup',
|
401
|
+
event_handler: mouse_event_handler,
|
402
|
+
},
|
403
|
+
{
|
404
|
+
event: 'contextmenu',
|
405
|
+
event_handler: context_menu_handler,
|
406
|
+
},
|
407
|
+
],
|
408
|
+
'on_mouse_down' => [
|
409
|
+
{
|
410
|
+
event: 'mousedown',
|
411
|
+
event_handler: mouse_event_handler,
|
412
|
+
},
|
413
|
+
{
|
414
|
+
event: 'contextmenu',
|
415
|
+
event_handler: context_menu_handler,
|
416
|
+
},
|
417
|
+
],
|
418
|
+
'on_swt_mouseup' => [
|
419
|
+
{
|
420
|
+
event: 'mouseup',
|
421
|
+
event_handler: mouse_event_handler,
|
422
|
+
},
|
423
|
+
{
|
424
|
+
event: 'contextmenu',
|
425
|
+
event_handler: context_menu_handler,
|
426
|
+
},
|
427
|
+
],
|
428
|
+
'on_swt_mousedown' => [
|
429
|
+
{
|
430
|
+
event: 'mousedown',
|
431
|
+
event_handler: mouse_event_handler,
|
432
|
+
},
|
433
|
+
{
|
434
|
+
event: 'contextmenu',
|
435
|
+
event_handler: context_menu_handler,
|
436
|
+
},
|
437
|
+
],
|
438
|
+
'on_key_pressed' => {
|
439
|
+
event: 'keypress',
|
440
|
+
event_handler: -> (event_listener) {
|
441
|
+
-> (event) {
|
442
|
+
event.define_singleton_method(:widget) {myself}
|
443
|
+
event.define_singleton_method(:keyLocation) do
|
444
|
+
location = `#{event.to_n}.originalEvent.location`
|
445
|
+
JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
|
446
|
+
end
|
447
|
+
event.define_singleton_method(:key_location, &event.method(:keyLocation))
|
448
|
+
event.define_singleton_method(:keyCode) {
|
449
|
+
JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
|
450
|
+
}
|
451
|
+
event.define_singleton_method(:key_code, &event.method(:keyCode))
|
452
|
+
event.define_singleton_method(:character) {event.which.chr}
|
453
|
+
event.define_singleton_method(:stateMask) do
|
454
|
+
state_mask = 0
|
455
|
+
state_mask |= SWTProxy[:alt] if event.alt_key
|
456
|
+
state_mask |= SWTProxy[:ctrl] if event.ctrl_key
|
457
|
+
state_mask |= SWTProxy[:shift] if event.shift_key
|
458
|
+
state_mask |= SWTProxy[:command] if event.meta_key
|
459
|
+
state_mask
|
460
|
+
end
|
461
|
+
event.define_singleton_method(:state_mask, &event.method(:stateMask))
|
462
|
+
doit = true
|
463
|
+
event.define_singleton_method(:doit=) do |value|
|
464
|
+
doit = value
|
465
|
+
end
|
466
|
+
event.define_singleton_method(:doit) { doit }
|
467
|
+
event_listener.call(event)
|
468
|
+
|
469
|
+
# TODO Fix doit false, it's not stopping input
|
470
|
+
unless doit
|
471
|
+
event.prevent
|
472
|
+
event.prevent_default
|
473
|
+
event.stop_propagation
|
474
|
+
event.stop_immediate_propagation
|
475
|
+
end
|
476
|
+
|
477
|
+
doit
|
478
|
+
}
|
479
|
+
} },
|
480
|
+
'on_key_released' => {
|
481
|
+
event: 'keyup',
|
482
|
+
event_handler: -> (event_listener) {
|
483
|
+
-> (event) {
|
484
|
+
event.define_singleton_method(:keyLocation) do
|
485
|
+
location = `#{event.to_n}.originalEvent.location`
|
486
|
+
JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
|
487
|
+
end
|
488
|
+
event.define_singleton_method(:key_location, &event.method(:keyLocation))
|
489
|
+
event.define_singleton_method(:widget) {myself}
|
490
|
+
event.define_singleton_method(:keyCode) {
|
491
|
+
JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
|
492
|
+
}
|
493
|
+
event.define_singleton_method(:key_code, &event.method(:keyCode))
|
494
|
+
event.define_singleton_method(:character) {event.which.chr}
|
495
|
+
event.define_singleton_method(:stateMask) do
|
496
|
+
state_mask = 0
|
497
|
+
state_mask |= SWTProxy[:alt] if event.alt_key
|
498
|
+
state_mask |= SWTProxy[:ctrl] if event.ctrl_key
|
499
|
+
state_mask |= SWTProxy[:shift] if event.shift_key
|
500
|
+
state_mask |= SWTProxy[:command] if event.meta_key
|
501
|
+
state_mask
|
502
|
+
end
|
503
|
+
event.define_singleton_method(:state_mask, &event.method(:stateMask))
|
504
|
+
doit = true
|
505
|
+
event.define_singleton_method(:doit=) do |value|
|
506
|
+
doit = value
|
507
|
+
end
|
508
|
+
event.define_singleton_method(:doit) { doit }
|
509
|
+
event_listener.call(event)
|
510
|
+
|
511
|
+
# TODO Fix doit false, it's not stopping input
|
512
|
+
unless doit
|
513
|
+
event.prevent
|
514
|
+
event.prevent_default
|
515
|
+
event.stop_propagation
|
516
|
+
event.stop_immediate_propagation
|
517
|
+
end
|
518
|
+
|
519
|
+
doit
|
520
|
+
}
|
521
|
+
}
|
522
|
+
},
|
523
|
+
'on_swt_keydown' => [
|
524
|
+
{
|
525
|
+
event: 'keypress',
|
526
|
+
event_handler: -> (event_listener) {
|
527
|
+
-> (event) {
|
528
|
+
event.define_singleton_method(:keyLocation) do
|
529
|
+
location = `#{event.to_n}.originalEvent.location`
|
530
|
+
JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
|
531
|
+
end
|
532
|
+
event.define_singleton_method(:key_location, &event.method(:keyLocation))
|
533
|
+
event.define_singleton_method(:keyCode) {
|
534
|
+
JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
|
535
|
+
}
|
536
|
+
event.define_singleton_method(:key_code, &event.method(:keyCode))
|
537
|
+
event.define_singleton_method(:widget) {myself}
|
538
|
+
event.define_singleton_method(:character) {event.which.chr}
|
539
|
+
event.define_singleton_method(:stateMask) do
|
540
|
+
state_mask = 0
|
541
|
+
state_mask |= SWTProxy[:alt] if event.alt_key
|
542
|
+
state_mask |= SWTProxy[:ctrl] if event.ctrl_key
|
543
|
+
state_mask |= SWTProxy[:shift] if event.shift_key
|
544
|
+
state_mask |= SWTProxy[:command] if event.meta_key
|
545
|
+
state_mask
|
546
|
+
end
|
547
|
+
event.define_singleton_method(:state_mask, &event.method(:stateMask))
|
548
|
+
doit = true
|
549
|
+
event.define_singleton_method(:doit=) do |value|
|
550
|
+
doit = value
|
551
|
+
end
|
552
|
+
event.define_singleton_method(:doit) { doit }
|
553
|
+
event_listener.call(event)
|
554
|
+
|
555
|
+
# TODO Fix doit false, it's not stopping input
|
556
|
+
unless doit
|
557
|
+
event.prevent
|
558
|
+
event.prevent_default
|
559
|
+
event.stop_propagation
|
560
|
+
event.stop_immediate_propagation
|
561
|
+
end
|
562
|
+
|
563
|
+
doit
|
564
|
+
}
|
565
|
+
}
|
566
|
+
},
|
567
|
+
{
|
568
|
+
event: 'keydown',
|
569
|
+
event_handler: -> (event_listener) {
|
570
|
+
-> (event) {
|
571
|
+
event.define_singleton_method(:keyLocation) do
|
572
|
+
location = `#{event.to_n}.originalEvent.location`
|
573
|
+
JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
|
574
|
+
end
|
575
|
+
event.define_singleton_method(:key_location, &event.method(:keyLocation))
|
576
|
+
event.define_singleton_method(:keyCode) {
|
577
|
+
JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
|
578
|
+
}
|
579
|
+
event.define_singleton_method(:key_code, &event.method(:keyCode))
|
580
|
+
event.define_singleton_method(:widget) {myself}
|
581
|
+
event.define_singleton_method(:character) {event.which.chr}
|
582
|
+
event.define_singleton_method(:stateMask) do
|
583
|
+
state_mask = 0
|
584
|
+
state_mask |= SWTProxy[:alt] if event.alt_key
|
585
|
+
state_mask |= SWTProxy[:ctrl] if event.ctrl_key
|
586
|
+
state_mask |= SWTProxy[:shift] if event.shift_key
|
587
|
+
state_mask |= SWTProxy[:command] if event.meta_key
|
588
|
+
state_mask
|
589
|
+
end
|
590
|
+
event.define_singleton_method(:state_mask, &event.method(:stateMask))
|
591
|
+
doit = true
|
592
|
+
event.define_singleton_method(:doit=) do |value|
|
593
|
+
doit = value
|
594
|
+
end
|
595
|
+
event.define_singleton_method(:doit) { doit }
|
596
|
+
event_listener.call(event) if event.which != 13 && (event.which == 127 || event.which <= 40)
|
597
|
+
|
598
|
+
# TODO Fix doit false, it's not stopping input
|
599
|
+
unless doit
|
600
|
+
event.prevent
|
601
|
+
event.prevent_default
|
602
|
+
event.stop_propagation
|
603
|
+
event.stop_immediate_propagation
|
604
|
+
end
|
605
|
+
doit
|
606
|
+
}
|
607
|
+
}
|
608
|
+
}
|
609
|
+
],
|
610
|
+
'on_swt_keyup' => {
|
611
|
+
event: 'keyup',
|
612
|
+
event_handler: -> (event_listener) {
|
613
|
+
-> (event) {
|
614
|
+
event.define_singleton_method(:keyLocation) do
|
615
|
+
location = `#{event.to_n}.originalEvent.location`
|
616
|
+
JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
|
617
|
+
end
|
618
|
+
event.define_singleton_method(:key_location, &event.method(:keyLocation))
|
619
|
+
event.define_singleton_method(:widget) {myself}
|
620
|
+
event.define_singleton_method(:keyCode) {
|
621
|
+
JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
|
622
|
+
}
|
623
|
+
event.define_singleton_method(:key_code, &event.method(:keyCode))
|
624
|
+
event.define_singleton_method(:character) {event.which.chr}
|
625
|
+
event.define_singleton_method(:stateMask) do
|
626
|
+
state_mask = 0
|
627
|
+
state_mask |= SWTProxy[:alt] if event.alt_key
|
628
|
+
state_mask |= SWTProxy[:ctrl] if event.ctrl_key
|
629
|
+
state_mask |= SWTProxy[:shift] if event.shift_key
|
630
|
+
state_mask |= SWTProxy[:command] if event.meta_key
|
631
|
+
state_mask
|
632
|
+
end
|
633
|
+
event.define_singleton_method(:state_mask, &event.method(:stateMask))
|
634
|
+
doit = true
|
635
|
+
event.define_singleton_method(:doit=) do |value|
|
636
|
+
doit = value
|
637
|
+
end
|
638
|
+
event.define_singleton_method(:doit) { doit }
|
639
|
+
event_listener.call(event)
|
640
|
+
|
641
|
+
# TODO Fix doit false, it's not stopping input
|
642
|
+
unless doit
|
643
|
+
event.prevent
|
644
|
+
event.prevent_default
|
645
|
+
event.stop_propagation
|
646
|
+
event.stop_immediate_propagation
|
647
|
+
end
|
648
|
+
|
649
|
+
doit
|
650
|
+
}
|
651
|
+
}
|
652
|
+
},
|
653
|
+
}
|
654
|
+
end
|
655
|
+
|
656
|
+
def name
|
657
|
+
self.class.name.split('::').last.underscore.sub(/_proxy$/, '').gsub('_', '-')
|
658
|
+
end
|
659
|
+
|
660
|
+
def id
|
661
|
+
return options[:id] if options.include?(:id)
|
662
|
+
@id ||= "#{name}-#{ElementProxy.next_id_number_for(name)}"
|
663
|
+
end
|
664
|
+
|
665
|
+
# Sets id explicitly. Useful in cases of wanting to maintain a stable id
|
666
|
+
def id=(value)
|
667
|
+
# TODO delete this method if it is not needed and isn't accurate in what it does
|
668
|
+
@id = value
|
669
|
+
end
|
670
|
+
|
671
|
+
# Subclasses can override with their own selector
|
672
|
+
def selector
|
673
|
+
"#{name}##{id}"
|
674
|
+
end
|
675
|
+
|
676
|
+
def add_css_class(css_class)
|
677
|
+
dom_element.add_class(css_class)
|
678
|
+
end
|
679
|
+
|
680
|
+
def add_css_classes(css_classes_to_add)
|
681
|
+
css_classes_to_add.each {|css_class| add_css_class(css_class)}
|
682
|
+
end
|
683
|
+
|
684
|
+
def remove_css_class(css_class)
|
685
|
+
dom_element.remove_class(css_class)
|
686
|
+
end
|
687
|
+
|
688
|
+
def remove_css_classes(css_classes_to_remove)
|
689
|
+
css_classes_to_remove.each {|css_class| remove_css_class(css_class)}
|
690
|
+
end
|
691
|
+
|
692
|
+
def clear_css_classes
|
693
|
+
css_classes.each {|css_class| remove_css_class(css_class)}
|
694
|
+
end
|
695
|
+
|
696
|
+
def has_style?(symbol)
|
697
|
+
@args.include?(symbol) # not a very solid implementation. Bring SWT constants eventually
|
698
|
+
end
|
699
|
+
|
700
|
+
def dom_element
|
701
|
+
# TODO consider making this pick an element in relation to its parent, allowing unhooked dom elements to be built if needed (unhooked to the visible page dom)
|
702
|
+
Document.find(path)
|
703
|
+
end
|
704
|
+
|
705
|
+
# TODO consider adding a default #dom method implementation for the common case, automatically relying on #element and other methods to build the dom html
|
706
|
+
|
707
|
+
def style_element
|
708
|
+
style_element_id = "#{id}-style"
|
709
|
+
style_element_selector = "style##{style_element_id}"
|
710
|
+
element = dom_element.find(style_element_selector)
|
711
|
+
if element.empty?
|
712
|
+
new_element = Element.new(:style)
|
713
|
+
new_element.attr('id', style_element_id)
|
714
|
+
new_element.attr('class', "#{name.gsub('_', '-')}-instance-style widget-instance-style")
|
715
|
+
dom_element.prepend(new_element)
|
716
|
+
element = dom_element.find(style_element_selector)
|
717
|
+
end
|
718
|
+
element
|
719
|
+
end
|
720
|
+
|
721
|
+
def listener_path
|
722
|
+
path
|
723
|
+
end
|
724
|
+
|
725
|
+
def listener_dom_element
|
726
|
+
Document.find(listener_path)
|
727
|
+
end
|
728
|
+
|
729
|
+
def observation_requests
|
730
|
+
@observation_requests ||= {}
|
731
|
+
end
|
732
|
+
|
733
|
+
def event_listener_proxies
|
734
|
+
@event_listener_proxies ||= []
|
735
|
+
end
|
736
|
+
|
737
|
+
def suspend_event_handling
|
738
|
+
@event_handling_suspended = true
|
739
|
+
end
|
740
|
+
|
741
|
+
def resume_event_handling
|
742
|
+
@event_handling_suspended = false
|
743
|
+
end
|
744
|
+
|
745
|
+
def event_handling_suspended?
|
746
|
+
@event_handling_suspended
|
747
|
+
end
|
748
|
+
|
749
|
+
def listeners
|
750
|
+
@listeners ||= {}
|
751
|
+
end
|
752
|
+
|
753
|
+
def listeners_for(listener_event)
|
754
|
+
listeners[listener_event.to_s] ||= []
|
755
|
+
end
|
756
|
+
|
757
|
+
def can_handle_observation_request?(observation_request)
|
758
|
+
# TODO sort this out for Opal
|
759
|
+
observation_request = observation_request.to_s
|
760
|
+
if observation_request.start_with?('on_swt_')
|
761
|
+
constant_name = observation_request.sub(/^on_swt_/, '')
|
762
|
+
SWTProxy.has_constant?(constant_name)
|
763
|
+
elsif observation_request.start_with?('on_')
|
764
|
+
# event = observation_request.sub(/^on_/, '')
|
765
|
+
# can_add_listener?(event) || can_handle_drag_observation_request?(observation_request) || can_handle_drop_observation_request?(observation_request)
|
766
|
+
true # TODO filter by valid listeners only in the future
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
770
|
+
def handle_observation_request(keyword, original_event_listener)
|
771
|
+
case keyword
|
772
|
+
when 'on_widget_removed'
|
773
|
+
listeners_for(keyword.sub(/^on_/, '')) << original_event_listener.to_proc
|
774
|
+
else
|
775
|
+
handle_javascript_observation_request(keyword, original_event_listener)
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
def handle_javascript_observation_request(keyword, original_event_listener)
|
780
|
+
return unless effective_observation_request_to_event_mapping.keys.include?(keyword)
|
781
|
+
event = nil
|
782
|
+
delegate = nil
|
783
|
+
effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
|
784
|
+
observation_requests[keyword] ||= Set.new
|
785
|
+
observation_requests[keyword] << original_event_listener
|
786
|
+
event = mapping[:event]
|
787
|
+
event_handler = mapping[:event_handler]
|
788
|
+
event_element_css_selector = mapping[:event_element_css_selector]
|
789
|
+
potential_event_listener = event_handler&.call(original_event_listener)
|
790
|
+
event_listener = potential_event_listener || original_event_listener
|
791
|
+
async_event_listener = proc do |event|
|
792
|
+
# TODO look into the issue with using async::task.new here. maybe put it in event listener (like not being able to call preventDefault or return false successfully )
|
793
|
+
# maybe consider pushing inside the widget classes instead where needed only or implement universal doit support correctly to bypass this issue
|
794
|
+
# Async::Task.new do
|
795
|
+
@@widget_handling_listener = self
|
796
|
+
# TODO also make sure to disable all widgets for suspension
|
797
|
+
event_listener.call(event) unless dialog_ancestor&.event_handling_suspended?
|
798
|
+
@widget_handling_listener = nil
|
799
|
+
# end
|
800
|
+
end
|
801
|
+
the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
|
802
|
+
unless the_listener_dom_element.empty?
|
803
|
+
the_listener_dom_element.on(event, &async_event_listener)
|
804
|
+
# TODO ensure uniqueness of insertion (perhaps adding equals/hash method to event listener proxy)
|
805
|
+
|
806
|
+
event_listener_proxies << EventListenerProxy.new(element_proxy: self, selector: selector, dom_element: the_listener_dom_element, event: event, listener: async_event_listener, original_event_listener: original_event_listener)
|
807
|
+
end
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
def remove_event_listener_proxies
|
812
|
+
event_listener_proxies.each do |event_listener_proxy|
|
813
|
+
event_listener_proxy.unregister
|
814
|
+
end
|
815
|
+
event_listener_proxies.clear
|
816
|
+
end
|
817
|
+
|
818
|
+
def add_observer(observer, property_name)
|
819
|
+
property_listener_installers = self.class&.ancestors&.to_a.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
|
820
|
+
widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
|
821
|
+
widget_listener_installers.to_a.each do |widget_listener_installer|
|
822
|
+
widget_listener_installer.call(observer)
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
def set_attribute(attribute_name, *args)
|
827
|
+
apply_property_type_converters(attribute_name, args)
|
828
|
+
super(attribute_name, *args) # PropertyOwner
|
829
|
+
end
|
830
|
+
|
831
|
+
def method_missing(method, *args, &block)
|
832
|
+
if method.to_s.start_with?('on_')
|
833
|
+
handle_observation_request(method, block)
|
834
|
+
else
|
835
|
+
super(method, *args, &block)
|
836
|
+
end
|
837
|
+
end
|
838
|
+
|
839
|
+
def swt_widget
|
840
|
+
# only added for compatibility/adaptibility with Glimmer DSL for SWT
|
841
|
+
self
|
842
|
+
end
|
843
|
+
|
844
|
+
def apply_property_type_converters(attribute_name, args)
|
845
|
+
if args.count == 1
|
846
|
+
value = args.first
|
847
|
+
converter = property_type_converters[attribute_name.to_sym]
|
848
|
+
args[0] = converter.call(value) if converter
|
849
|
+
end
|
850
|
+
# if args.count == 1 && args.first.is_a?(ColorProxy)
|
851
|
+
# g_color = args.first
|
852
|
+
# args[0] = g_color.swt_color
|
853
|
+
# end
|
854
|
+
end
|
855
|
+
|
856
|
+
def property_type_converters
|
857
|
+
color_converter = proc do |value|
|
858
|
+
if value.is_a?(Symbol) || value.is_a?(String)
|
859
|
+
ColorProxy.new(value)
|
860
|
+
else
|
861
|
+
value
|
862
|
+
end
|
863
|
+
end
|
864
|
+
@property_type_converters ||= {
|
865
|
+
:background => color_converter,
|
866
|
+
# :background_image => proc do |value|
|
867
|
+
# if value.is_a?(String)
|
868
|
+
# if value.start_with?('uri:classloader')
|
869
|
+
# value = value.sub(/^uri\:classloader\:\//, '')
|
870
|
+
# object = java.lang.Object.new
|
871
|
+
# value = object.java_class.resource_as_stream(value)
|
872
|
+
# value = java.io.BufferedInputStream.new(value)
|
873
|
+
# end
|
874
|
+
# image_data = ImageData.new(value)
|
875
|
+
# on_event_Resize do |resize_event|
|
876
|
+
# new_image_data = image_data.scaledTo(@swt_widget.getSize.x, @swt_widget.getSize.y)
|
877
|
+
# @swt_widget.getBackgroundImage&.remove
|
878
|
+
# @swt_widget.setBackgroundImage(Image.new(@swt_widget.getDisplay, new_image_data))
|
879
|
+
# end
|
880
|
+
# Image.new(@swt_widget.getDisplay, image_data)
|
881
|
+
# else
|
882
|
+
# value
|
883
|
+
# end
|
884
|
+
# end,
|
885
|
+
:foreground => color_converter,
|
886
|
+
# :font => proc do |value|
|
887
|
+
# if value.is_a?(Hash)
|
888
|
+
# font_properties = value
|
889
|
+
# FontProxy.new(self, font_properties).swt_font
|
890
|
+
# else
|
891
|
+
# value
|
892
|
+
# end
|
893
|
+
# end,
|
894
|
+
:text => proc do |value|
|
895
|
+
# if swt_widget.is_a?(Browser)
|
896
|
+
# value.to_s
|
897
|
+
# else
|
898
|
+
value.to_s
|
899
|
+
# end
|
900
|
+
end,
|
901
|
+
# :visible => proc do |value|
|
902
|
+
# !!value
|
903
|
+
# end,
|
904
|
+
}
|
905
|
+
end
|
906
|
+
|
907
|
+
def widget_property_listener_installers
|
908
|
+
@swt_widget_property_listener_installers ||= {
|
909
|
+
# WidgetProxy => {
|
910
|
+
# :focus => proc do |observer|
|
911
|
+
# on_focus_gained { |focus_event|
|
912
|
+
# observer.call(true)
|
913
|
+
# }
|
914
|
+
# on_focus_lost { |focus_event|
|
915
|
+
# observer.call(false)
|
916
|
+
# }
|
917
|
+
# end,
|
918
|
+
# },
|
919
|
+
MenuItemProxy => {
|
920
|
+
:selection => proc do |observer|
|
921
|
+
on_widget_selected { |selection_event|
|
922
|
+
# TODO look into validity of this and perhaps move toggle logic to MenuItemProxy
|
923
|
+
if check?
|
924
|
+
observer.call(!selection)
|
925
|
+
else
|
926
|
+
observer.call(selection)
|
927
|
+
end
|
928
|
+
}
|
929
|
+
end
|
930
|
+
},
|
931
|
+
ScaleProxy => {
|
932
|
+
:selection => proc do |observer|
|
933
|
+
on_widget_selected { |selection_event|
|
934
|
+
observer.call(selection)
|
935
|
+
}
|
936
|
+
end
|
937
|
+
},
|
938
|
+
SliderProxy => {
|
939
|
+
:selection => proc do |observer|
|
940
|
+
on_widget_selected { |selection_event|
|
941
|
+
observer.call(selection)
|
942
|
+
}
|
943
|
+
end
|
944
|
+
},
|
945
|
+
SpinnerProxy => {
|
946
|
+
:selection => proc do |observer|
|
947
|
+
on_widget_selected { |selection_event|
|
948
|
+
observer.call(selection)
|
949
|
+
}
|
950
|
+
end
|
951
|
+
},
|
952
|
+
TextProxy => {
|
953
|
+
:text => proc do |observer|
|
954
|
+
on_modify_text { |modify_event|
|
955
|
+
observer.call(text)
|
956
|
+
}
|
957
|
+
end,
|
958
|
+
# :caret_position => proc do |observer|
|
959
|
+
# on_event_keydown { |event|
|
960
|
+
# observer.call(getCaretPosition)
|
961
|
+
# }
|
962
|
+
# on_event_keyup { |event|
|
963
|
+
# observer.call(getCaretPosition)
|
964
|
+
# }
|
965
|
+
# on_event_mousedown { |event|
|
966
|
+
# observer.call(getCaretPosition)
|
967
|
+
# }
|
968
|
+
# on_event_mouseup { |event|
|
969
|
+
# observer.call(getCaretPosition)
|
970
|
+
# }
|
971
|
+
# end,
|
972
|
+
# :selection => proc do |observer|
|
973
|
+
# on_event_keydown { |event|
|
974
|
+
# observer.call(getSelection)
|
975
|
+
# }
|
976
|
+
# on_event_keyup { |event|
|
977
|
+
# observer.call(getSelection)
|
978
|
+
# }
|
979
|
+
# on_event_mousedown { |event|
|
980
|
+
# observer.call(getSelection)
|
981
|
+
# }
|
982
|
+
# on_event_mouseup { |event|
|
983
|
+
# observer.call(getSelection)
|
984
|
+
# }
|
985
|
+
# end,
|
986
|
+
# :selection_count => proc do |observer|
|
987
|
+
# on_event_keydown { |event|
|
988
|
+
# observer.call(getSelectionCount)
|
989
|
+
# }
|
990
|
+
# on_event_keyup { |event|
|
991
|
+
# observer.call(getSelectionCount)
|
992
|
+
# }
|
993
|
+
# on_event_mousedown { |event|
|
994
|
+
# observer.call(getSelectionCount)
|
995
|
+
# }
|
996
|
+
# on_event_mouseup { |event|
|
997
|
+
# observer.call(getSelectionCount)
|
998
|
+
# }
|
999
|
+
# end,
|
1000
|
+
# :top_index => proc do |observer|
|
1001
|
+
# @last_top_index = getTopIndex
|
1002
|
+
# on_paint_control { |event|
|
1003
|
+
# if getTopIndex != @last_top_index
|
1004
|
+
# @last_top_index = getTopIndex
|
1005
|
+
# observer.call(@last_top_index)
|
1006
|
+
# end
|
1007
|
+
# }
|
1008
|
+
# end,
|
1009
|
+
},
|
1010
|
+
# Java::OrgEclipseSwtCustom::StyledText => {
|
1011
|
+
# :text => proc do |observer|
|
1012
|
+
# on_modify_text { |modify_event|
|
1013
|
+
# observer.call(getText)
|
1014
|
+
# }
|
1015
|
+
# end,
|
1016
|
+
# },
|
1017
|
+
DateTimeProxy => {
|
1018
|
+
:date_time => proc do |observer|
|
1019
|
+
on_widget_selected { |selection_event|
|
1020
|
+
observer.call(date_time)
|
1021
|
+
}
|
1022
|
+
end
|
1023
|
+
},
|
1024
|
+
RadioProxy => { #radio?
|
1025
|
+
:selection => proc do |observer|
|
1026
|
+
on_widget_selected { |selection_event|
|
1027
|
+
observer.call(selection)
|
1028
|
+
}
|
1029
|
+
end
|
1030
|
+
},
|
1031
|
+
TableProxy => {
|
1032
|
+
:selection => proc do |observer|
|
1033
|
+
on_widget_selected { |selection_event|
|
1034
|
+
observer.call(selection_event.table_item.get_data) # TODO ensure selection doesn't conflict with editing
|
1035
|
+
}
|
1036
|
+
end,
|
1037
|
+
},
|
1038
|
+
# Java::OrgEclipseSwtWidgets::MenuItem => {
|
1039
|
+
# :selection => proc do |observer|
|
1040
|
+
# on_widget_selected { |selection_event|
|
1041
|
+
# observer.call(getSelection)
|
1042
|
+
# }
|
1043
|
+
# end
|
1044
|
+
# },
|
1045
|
+
}
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
private
|
1049
|
+
|
1050
|
+
def css_cursor
|
1051
|
+
SWT_CURSOR_TO_CSS_CURSOR_MAP[@cursor]
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
require 'glimmer/dsl/web/element_expression'
|